src/Controller/DefaultController.php line 558

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Client\StripeClient;
  4. use App\Dto\Slot;
  5. use App\Entity\Account;
  6. use App\Entity\CustomerProfile;
  7. use App\Entity\GroupSession;
  8. use App\Entity\Need;
  9. use App\Entity\Partner;
  10. use App\Entity\PartnerNote;
  11. use App\Entity\PartnerSecondaryLocation;
  12. use App\Entity\PartnerSubscription;
  13. use App\Entity\Reservation;
  14. use App\Entity\ReservationPayment;
  15. use App\Entity\ReservationPaymentLog;
  16. use App\Entity\ServiceDelivery;
  17. use App\Entity\ServiceDeliveryPlace;
  18. use App\Entity\Speciality;
  19. use App\Entity\User;
  20. use App\Entity\VillesFrance;
  21. use App\Form\PartnerNoteType;
  22. use App\Form\UserLoginType;
  23. use App\Form\UserRegisterType;
  24. use App\Security\PasswordEncoder;
  25. use App\Service\GoogleCalendarService;
  26. use App\Service\ICalService;
  27. use App\Service\MailjetEmailService;
  28. use App\Utils\AgendaUtils;
  29. use Doctrine\ORM\EntityManagerInterface;
  30. use Stripe\Exception\ApiErrorException;
  31. use Symfony\Bridge\Doctrine\Form\Type\EntityType;
  32. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  33. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  34. use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
  35. use Symfony\Component\Form\Extension\Core\Type\EmailType;
  36. use Symfony\Component\Form\Extension\Core\Type\PasswordType;
  37. use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
  38. use Symfony\Component\Form\FormInterface;
  39. use Symfony\Component\HttpFoundation\Exception\BadRequestException;
  40. use Symfony\Component\HttpFoundation\Request;
  41. use Symfony\Component\HttpFoundation\Response;
  42. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  43. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  44. use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
  45. use Symfony\Component\Routing\Annotation\Route;
  46. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  47. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  48. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  49. use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
  50. use Symfony\Component\Security\Http\SecurityEvents;
  51. use Symfony\Component\Validator\Constraints\NotBlank;
  52. use ICal\ICal;
  53. /**
  54. * Class Defaultcontroller
  55. * @package App\Controller
  56. *
  57. * @Route("/", name="default_")
  58. */
  59. class DefaultController extends AbstractController
  60. {
  61. private $em;
  62. private $agendaUtils;
  63. private $passwordEncoder;
  64. private $session;
  65. private $tokenStorage;
  66. private $eventDispatcher;
  67. private $stripeClient;
  68. private $mailjetEmailService;
  69. private $icalService;
  70. private $googleCalendarService;
  71. public function __construct(EntityManagerInterface $em, AgendaUtils $agendaUtils, PasswordEncoder $passwordEncoder, SessionInterface $session, TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher, StripeClient $stripeClient, MailjetEmailService $mailjetEmailService, ICalService $icalService, GoogleCalendarService $googleCalendarService)
  72. {
  73. $this->em = $em;
  74. $this->agendaUtils = $agendaUtils;
  75. $this->passwordEncoder = $passwordEncoder;
  76. $this->session = $session;
  77. $this->tokenStorage = $tokenStorage;
  78. $this->eventDispatcher = $eventDispatcher;
  79. $this->stripeClient = $stripeClient;
  80. $this->mailjetEmailService = $mailjetEmailService;
  81. $this->icalService = $icalService;
  82. $this->googleCalendarService = $googleCalendarService;
  83. }
  84. /**
  85. * @Route(
  86. * path="/",
  87. * name="home",
  88. * options={"sitemap" = true}
  89. * )
  90. *
  91. * @return Response
  92. */
  93. public function homeAction(Request $request, EntityManagerInterface $em)
  94. {
  95. $needs = $em->getRepository(Need::class)->findBy([], ["label" => "ASC"]);
  96. $specialities = $em->getRepository(Speciality::class)->findBy([], ["label" => "ASC"]);
  97. return $this->render('index.html.twig', [
  98. "needs" => $needs,
  99. "specialities" => $specialities,
  100. ]);
  101. }
  102. /**
  103. * @Route(
  104. * path="/login",
  105. * name="login",
  106. * options={"sitemap" = true}
  107. * )
  108. *
  109. * @return Response
  110. */
  111. public function loginAction(Request $request, EntityManagerInterface $em)
  112. {
  113. $user = new User();
  114. $formRegister = $this->createForm(UserRegisterType::class, $user);
  115. $registerError = null;
  116. $registerSuccess = null;
  117. $account = new Account();
  118. $formLogin = $this->createForm(UserLoginType::class, $account);
  119. $loginError = null;
  120. $handleRegister = $this->handleRegister($formRegister, $user, $request);
  121. if ($handleRegister) {
  122. if ($handleRegister['success']) {
  123. $registerSuccess = "Bienvenue sur ZenDez-Vous ! Votre compte a été créé avec succès. Vous pouvez dès maintenant vous connecter.";
  124. } else {
  125. $registerError = $handleRegister['message'];
  126. }
  127. }
  128. $handleLogin = $this->handleLogin($formLogin, $request);
  129. if ($handleLogin) {
  130. if ($handleLogin['success']) {
  131. return $this->redirectToRoute('default_home');
  132. } else {
  133. $loginError = $handleLogin['message'];
  134. }
  135. }
  136. return $this->render('login.html.twig', [
  137. "formRegister" => $formRegister->createView(),
  138. "registerError" => $registerError,
  139. "registerSuccess" => $registerSuccess,
  140. "formLogin" => $formLogin->createView(),
  141. "loginError" => $loginError,
  142. ]);
  143. }
  144. /**
  145. * @Route(
  146. * path="/login-pro",
  147. * name="login_pro",
  148. * )
  149. *
  150. * @return Response
  151. */
  152. public function loginProAction(Request $request, EntityManagerInterface $em)
  153. {
  154. return $this->render('login-pro.html.twig', [
  155. ]);
  156. }
  157. private function handleRegister(FormInterface $form, User $user, Request $request)
  158. {
  159. $form->handleRequest($request);
  160. if ($form->isSubmitted() && $form->isValid()) {
  161. $account = $this->em->getRepository(Account::class)->findOneBy(['email' => $form->get('email')->getData()]);
  162. if (!$account) {
  163. if ($form->get('password')->getData() == $form->get('password2')->getData()) {
  164. $this->em->persist($user);
  165. $this->em->flush();
  166. $account = new Account();
  167. $account->setEmail($form->get('email')->getData());
  168. $account->setRegistrationDate(new \DateTime('now'));
  169. $salt = md5(uniqid());
  170. $account->setSalt($salt);
  171. $enc_pwd = $this->passwordEncoder->encodePassword($form->get('password')->getData(), $salt);
  172. $account->setPassword($enc_pwd);
  173. $account->setEnabled(true);
  174. $account->setUser($user);
  175. $account->setRoles(["ROLE_USER"]);
  176. $this->em->persist($account);
  177. $this->em->flush();
  178. $stripeCustomer = $this->stripeClient->createCustomer($account, $account->getEmail(), $user->getFirstName(), $user->getLastName());
  179. if ($stripeCustomer && $stripeCustomer->id) {
  180. $account->setStripeCustomerId($stripeCustomer->id);
  181. $this->em->flush();
  182. }
  183. $this->mailjetEmailService->send(
  184. "Bienvenue sur ZenDez-Vous",
  185. $account->getEmail(),
  186. 4686131,
  187. [
  188. "title" => "Bienvenue !",
  189. "content" => "Bonjour " . $user->getFirstName() . "<br/><br/>L'équipe ZenDez-Vous vous remercie pour votre inscription.<br/>Accèdez dès maintenant à des milliers de créneaux disponibles avec nos praticiens.<br/><br/>Vous ne savez pas par où commencer ? Prenez rendez-vous avec un de nos coachs pour faire le point sur vos besoins et obtenir un parcours de bien-être approprié.",
  190. "email" => $account->getEmail(),
  191. ]);
  192. return [
  193. 'success' => true,
  194. ];
  195. } else {
  196. return [
  197. 'success' => false,
  198. 'message' => 'Les mots de passes ne correspondent pas.',
  199. ];
  200. }
  201. } else {
  202. return [
  203. 'success' => false,
  204. 'message' => 'Cet adresse email est déjà utilisé.',
  205. ];
  206. }
  207. }
  208. return null;
  209. }
  210. private function handleLogin(FormInterface $form, Request $request)
  211. {
  212. $form->handleRequest($request);
  213. if ($form->isSubmitted() && $form->isValid()) {
  214. $account = $this->em->getRepository(Account::class)->findOneBy(['email' => $form->get('email')->getData()]);
  215. if ($account) {
  216. $isValid = $this->passwordEncoder->isPasswordValid($account->getPassword(), $form->get('password')->getData(), $account->getSalt());
  217. if ($isValid) {
  218. if ($account->getEnabled()) {
  219. $firewall = 'main';
  220. $token = new UsernamePasswordToken($account, $firewall, $account->getRoles());
  221. $this->tokenStorage->setToken($token);
  222. $this->session->set('_security_' . $firewall, serialize($token));
  223. $event = new InteractiveLoginEvent($request, $token);
  224. $this->eventDispatcher->dispatch($event, SecurityEvents::INTERACTIVE_LOGIN);
  225. $account->setLastLogin(new \DateTime('now'));
  226. $this->em->flush();
  227. return [
  228. 'success' => true,
  229. ];
  230. } else {
  231. return [
  232. 'success' => false,
  233. 'message' => 'Votre compte n\'est pas actif.',
  234. ];
  235. }
  236. } else {
  237. return [
  238. 'success' => false,
  239. 'message' => 'Identifiant et/ou mot de passe incorrect.',
  240. ];
  241. }
  242. } else {
  243. return [
  244. 'success' => false,
  245. 'message' => 'Identifiant et/ou mot de passe incorrect.',
  246. ];
  247. }
  248. }
  249. return null;
  250. }
  251. /**
  252. * @Route(
  253. * path="/logout",
  254. * name="logout"
  255. * )
  256. *
  257. * @return Response
  258. */
  259. public function logoutAction(Request $request, EntityManagerInterface $em)
  260. {
  261. $this->tokenStorage->setToken(null);
  262. $this->session->invalidate();
  263. return $this->redirectToRoute("default_home");
  264. }
  265. /**
  266. * @Route(
  267. * path="/forgot",
  268. * name="forgot_password"
  269. * )
  270. *
  271. * @return Response
  272. */
  273. public function forgotPasswordAction(Request $request, EntityManagerInterface $em, MailjetEmailService $mailjetEmailService)
  274. {
  275. $form = $this->createFormBuilder()
  276. ->add('email', EmailType::class, array(
  277. 'attr' => array(
  278. 'placeholder' => 'Adresse mail',
  279. ),
  280. 'required' => true,
  281. ))
  282. ->getForm();
  283. $form->handleRequest($request);
  284. $success = null;
  285. $error = null;
  286. if ($form->isSubmitted()) {
  287. if ($form->isValid()) {
  288. $data = $form->getData();
  289. $account = $em->getRepository(Account::class)->findOneBy(["email" => $data["email"]]);
  290. if ($account) {
  291. if ($account->getEnabled()) {
  292. $now = new \DateTime('now');
  293. if (!$account->getPasswordRequestDate() || $now->getTimestamp() - $account->getPasswordRequestDate()->getTimestamp() >= 86400) {
  294. $token = hash("sha256", uniqid());
  295. $account->setPasswordRequest($token);
  296. $account->setPasswordRequestDate(new \DateTime('now'));
  297. $em->flush();
  298. $url = $this->generateUrl("default_reset_password", ["email" => $account->getEmail(), "token" => $token], UrlGeneratorInterface::ABSOLUTE_URL);
  299. $content = <<<EOD
  300. <br/>
  301. Vous avez effectué une demande de réinitialisation de mot de passe pour votre compte ZenDez-Vous<br/>
  302. <br/>
  303. Pour définir un nouveau mot de passe, rendez-vous sur le lien suivant:<br/>
  304. <a href="$url">$url</a>
  305. EOD;
  306. $mailjetEmailService->send(
  307. "ZenDez-Vous: réinitialisation de votre mot de passe",
  308. $account->getEmail(),
  309. 4686131,
  310. [
  311. "title" => "Réinitialisation de votre mot de passe",
  312. "content" => $content,
  313. "email" => $account->getEmail(),
  314. ]);
  315. $success = "Si un compte existe à cette adresse email, un mail contenant un lien de réinitialisation a été envoyé à l'adresse indiquée. Ce lien est valide pendant 24h.";
  316. } else {
  317. $error = "Une demande de réinitialisation est déjà en cours. Veuillez vérifier votre boite mail.";
  318. }
  319. } else {
  320. $error = "Votre compte n'est pas actif";
  321. }
  322. } else {
  323. $success = "Si un compte existe à cette adresse email, un mail contenant un lien de réinitialisation a été envoyé à l'adresse indiquée. Ce lien est valide pendant 24h.";
  324. }
  325. } else {
  326. $error = "Merci de saisir une adresse mail valide";
  327. }
  328. }
  329. return $this->render('forgot.html.twig', [
  330. "form" => $form->createView(),
  331. "success" => $success,
  332. "error" => $error,
  333. ]);
  334. }
  335. /**
  336. * @Route(
  337. * path="/reset-password/{email}/{token}",
  338. * name="reset_password"
  339. * )
  340. *
  341. * @return Response
  342. */
  343. public function resetPasswordAction(Request $request, EntityManagerInterface $em, PasswordEncoder $passwordEncoder)
  344. {
  345. $account = $em->getRepository(Account::class)->findOneBy(["email" => $request->get("email")]);
  346. if ($account) {
  347. if ($account->getPasswordRequest() && $account->getPasswordRequestDate()) {
  348. if ($account->getPasswordRequest() == $request->get("token")) {
  349. $now = new \DateTime('now');
  350. if ($now->getTimestamp() - $account->getPasswordRequestDate()->getTimestamp() < 86400) {
  351. $form = $this->createFormBuilder()
  352. ->add('password', RepeatedType::class, array(
  353. 'type' => PasswordType::class,
  354. 'invalid_message' => 'Les mot de passe ne sont pas identiques',
  355. 'first_options' => array(
  356. "label" => 'Nouveau mot de passe',
  357. 'attr' => array(
  358. 'placeholder' => ''
  359. ),
  360. ),
  361. 'second_options' => array("label" => 'Confirmation mot de passe', 'attr' => array('placeholder' => '')),
  362. ))->getForm();
  363. $form->handleRequest($request);
  364. if ($form->isSubmitted() && $form->isValid()) {
  365. $salt = md5(uniqid());
  366. $pwd = $form['password']->getData();
  367. $account->setSalt($salt);
  368. $enc_pwd = $passwordEncoder->encodePassword($pwd, $salt);
  369. $account->setPassword($enc_pwd);
  370. $account->setPasswordRequest(null);
  371. $account->setPasswordRequestDate(null);
  372. $em->flush();
  373. return $this->redirectToRoute("default_login");
  374. }
  375. return $this->render('reset_pwd.html.twig', [
  376. "form" => $form->createView(),
  377. ]);
  378. } else {
  379. throw new NotFoundHttpException("Cette URL n'est plus valide");
  380. }
  381. } else {
  382. throw new NotFoundHttpException("Cette URL n'est pas valide");
  383. }
  384. }
  385. }
  386. throw new NotFoundHttpException();
  387. }
  388. /**
  389. * @Route(
  390. * path="/join-us",
  391. * name="join-us",
  392. * )
  393. *
  394. * @return Response
  395. */
  396. public function joinUsAction(Request $request, EntityManagerInterface $em)
  397. {
  398. return $this->redirectToRoute('default_nous-rejoindre', [], 301);
  399. }
  400. /**
  401. * @Route(
  402. * path="/nous-rejoindre",
  403. * name="nous-rejoindre",
  404. * options={"sitemap" = true}
  405. * )
  406. *
  407. * @return Response
  408. */
  409. public function nousRejoindreAction(Request $request, EntityManagerInterface $em)
  410. {
  411. return $this->render('join-us.html.twig', []);
  412. }
  413. /**
  414. * @Route(
  415. * path="/formations",
  416. * name="formations",
  417. * options={"sitemap" = true}
  418. * )
  419. *
  420. * @return Response
  421. */
  422. public function formationsAction(Request $request, EntityManagerInterface $em)
  423. {
  424. return $this->render('formations.html.twig', [
  425. ]);
  426. }
  427. /**
  428. * @Route(
  429. * path="/formations-partenaires",
  430. * name="formations-partenaires",
  431. * options={"sitemap" = true}
  432. * )
  433. *
  434. * @return Response
  435. */
  436. public function formationsPartenairesAction(Request $request, EntityManagerInterface $em)
  437. {
  438. return $this->render('formations-partenaires.html.twig', [
  439. ]);
  440. }
  441. /**
  442. * @Route(
  443. * path="/are-you-practitioner",
  444. * name="are-you-practitioner",
  445. * )
  446. *
  447. * @return Response
  448. */
  449. public function areYouPractitionerAction(Request $request, EntityManagerInterface $em)
  450. {
  451. return $this->redirectToRoute('default_professionnels-du-bien-etre', [], 301);
  452. }
  453. /**
  454. * @Route(
  455. * path="/professionnels-du-bien-etre",
  456. * name="professionnels-du-bien-etre",
  457. * options={"sitemap" = true}
  458. * )
  459. *
  460. * @return Response
  461. */
  462. public function professionnelsDuBienEtreAction(Request $request, EntityManagerInterface $em)
  463. {
  464. return $this->render('are-you-practitioner.html.twig', []);
  465. }
  466. /**
  467. * @Route(
  468. * path="/offre-entreprise",
  469. * name="offre-entreprise",
  470. * options={"sitemap" = true}
  471. * )
  472. *
  473. * @return Response
  474. */
  475. public function offreEntrepriseAction(Request $request, EntityManagerInterface $em)
  476. {
  477. return $this->render('offre-entreprise.html.twig', [
  478. ]);
  479. }
  480. /**
  481. * @Route(
  482. * path="/conseils",
  483. * name="conseils",
  484. * options={"sitemap" = true}
  485. * )
  486. *
  487. * @return Response
  488. */
  489. public function conseilsAction(Request $request, EntityManagerInterface $em)
  490. {
  491. return $this->render('conseils.html.twig', [
  492. ]);
  493. }
  494. /**
  495. * @Route(
  496. * path="/besoins",
  497. * name="besoins",
  498. * options={"sitemap" = true}
  499. * )
  500. *
  501. * @return Response
  502. */
  503. public function besoinsAction(Request $request, EntityManagerInterface $em)
  504. {
  505. $needs = $em->getRepository(Need::class)->findBy([], ["label" => "ASC"]);
  506. return $this->render('besoins.html.twig', [
  507. "needs" => $needs,
  508. ]);
  509. }
  510. /**
  511. * @Route(
  512. * path="/mentions-legales",
  513. * name="mentions_legales",
  514. * options={"sitemap" = true}
  515. * )
  516. *
  517. * @return Response
  518. */
  519. public function mentionsLegalesAction(Request $request, EntityManagerInterface $em)
  520. {
  521. return $this->render('mentions-legales.html.twig', [
  522. ]);
  523. }
  524. /**
  525. * @Route(
  526. * path="/besoins/{slug}",
  527. * name="besoins_fiche"
  528. * )
  529. *
  530. * @return Response
  531. */
  532. public function besoinsFicheAction(Need $need, Request $request, EntityManagerInterface $em)
  533. {
  534. return $this->render('besoins/fiche.html.twig', [
  535. "need" => $need,
  536. ]);
  537. }
  538. /**
  539. * @Route(
  540. * path="/pratiques",
  541. * name="pratiques",
  542. * )
  543. *
  544. * @return Response
  545. */
  546. public function pratiquesAction(Request $request, EntityManagerInterface $em)
  547. {
  548. return $this->redirectToRoute('praticiens_list', [], 301);
  549. }
  550. /**
  551. * @Route(
  552. * path="/pratiques/{slug}",
  553. * name="pratiques_fiche"
  554. * )
  555. *
  556. * @return Response
  557. */
  558. public function pratiquesFicheAction(Speciality $speciality, Request $request, EntityManagerInterface $em)
  559. {
  560. return $this->redirectToRoute('praticiens_description', ["slugJob" => $speciality->getSlugJob()], 301);
  561. }
  562. /**
  563. * @Route(
  564. * path="/search",
  565. * name="search",
  566. * options={"sitemap" = true}
  567. * )
  568. *
  569. * @return Response
  570. */
  571. public function searchAction(Request $request, EntityManagerInterface $em)
  572. {
  573. $type = "practitionner";
  574. $terms = $request->get('search');
  575. $keywords= null;
  576. if ($terms) {
  577. $termsExplode = explode(" ", $terms);
  578. if (count($termsExplode)) {
  579. $keywords = $termsExplode;
  580. }
  581. }
  582. $location = $request->get('location');
  583. $lat = $request->get('lat');
  584. if ($lat && $location) { $lat = floatval($lat); } else { $lat = null; }
  585. $lon = $request->get('lon');
  586. if ($lon && $location) { $lon = floatval($lon); } else { $lon = null; }
  587. $radius = $request->get('rad');
  588. if (!$radius || intval($radius) < 5 || intval($radius) > 50) {
  589. $radius = 50;
  590. } else {
  591. $radius = intval($radius);
  592. }
  593. if ($location && (!$lat || !$lon)) {
  594. $locationKeywords = null;
  595. $locationExplode = explode(" ", $location);
  596. if (count($locationExplode)) {
  597. $locationKeywords = $locationExplode;
  598. }
  599. $cities = $this->em->getRepository(VillesFrance::class)->findByTerms($locationKeywords);
  600. if ($cities) {
  601. $city = $cities[0];
  602. if ($city) {
  603. $location = $city->getVilleNomReel();
  604. $lat = $city->getVilleLatitudeDeg();
  605. $lon = $city->getVilleLongitudeDeg();
  606. }
  607. }
  608. }
  609. $partners = $em->getRepository(Partner::class)->search($type, $keywords, $lat, $lon, $radius);
  610. $partnersAllowed = $this::checkPartnerAllowed($partners);
  611. // Autres en téléconsultation
  612. $othersOnVisio = false;
  613. if (count($partnersAllowed) == 0 && $lat && $lon) {
  614. $partners = $em->getRepository(Partner::class)->search($type, $keywords, null, null, null, null, true);
  615. $partnersAllowed = $this::checkPartnerAllowed($partners);
  616. if (count($partnersAllowed) > 0) {
  617. $othersOnVisio = true;
  618. }
  619. }
  620. $specialitiesJobString = null;
  621. $specialitiesJobsString = null;
  622. if ($terms) {
  623. $specialities = $this->em->getRepository(Speciality::class)->findByTerms($keywords);
  624. $specialitiesJob = [];
  625. $specialitiesJobs = [];
  626. if ($specialities) {
  627. foreach ($specialities as $speciality) {
  628. $specialitiesJob[] = $speciality->getJob();
  629. $specialitiesJobs[] = $speciality->getJobs();
  630. }
  631. $specialitiesJobString = implode(', ', $specialitiesJob);
  632. $specialitiesJobsString = implode(', ', $specialitiesJobs);
  633. }
  634. }
  635. return $this->render('search.html.twig', [
  636. "type" => $type,
  637. "partners" => $partnersAllowed,
  638. "terms" => $terms,
  639. "location" => $location,
  640. "lat" => $request->get('lat'),
  641. "lon" => $request->get('lon'),
  642. "rad" => $radius,
  643. "othersOnVisio" => $othersOnVisio,
  644. "specialitiesJobString" => $specialitiesJobString,
  645. "specialitiesJobsString" => $specialitiesJobsString,
  646. ]);
  647. }
  648. private function checkPartnerAllowed($partners)
  649. {
  650. return $this::checkPartnersAlloweds($partners);
  651. }
  652. public static function checkPartnersAlloweds($partners)
  653. {
  654. $partnersAllowed = [];
  655. foreach ($partners as $p) {
  656. $partner = $p[0];
  657. if ($partner->getIsActivePractitioner()) {
  658. $partnersAllowed[] = $partner;
  659. }
  660. }
  661. return $partnersAllowed;
  662. }
  663. /**
  664. * @Route(
  665. * path="/praticien/{slug}",
  666. * name="praticien_fiche"
  667. * )
  668. *
  669. * @return Response
  670. */
  671. public function praticienFicheAction(Partner $partner, Request $request, EntityManagerInterface $em)
  672. {
  673. if (!$partner->getIspractitioner()) {
  674. throw new NotFoundHttpException();
  675. }
  676. if (!$partner->isSubscriptionActive() || !$partner->isValidatedPractitioner()) {
  677. return $this->redirectToRoute("default_home");
  678. }
  679. $form = null;
  680. $partnerNote = null;
  681. if ($this->getUser() && $this->getUser()->getUser()) {
  682. $partnerNote = $em->getRepository(PartnerNote::class)->findOneBy(["partner" => $partner, "user" => $this->getUser()->getUser()]);
  683. if (!$partnerNote) {
  684. $partnerNote = new PartnerNote();
  685. $partnerNote->setUser($this->getUser()->getUser());
  686. $partnerNote->setPartner($partner);
  687. }
  688. $form = $this->createForm(PartnerNoteType::class, $partnerNote);
  689. $form->handleRequest($request);
  690. if ($form->isSubmitted() && $form->isValid()) {
  691. $em->persist($partnerNote);
  692. $em->flush();
  693. }
  694. }
  695. $groupSessions = $em->getRepository(GroupSession::class)->findAvailablesByPartner($partner);
  696. return $this->render('praticien/fiche.html.twig', [
  697. "practitioner" => $partner,
  698. "form" => $form ? $form->createView() : null,
  699. "partnerNote" => $partnerNote,
  700. "groupSessions" => $groupSessions
  701. ]);
  702. }
  703. /**
  704. * @Route(
  705. * path="/checkout/identification",
  706. * name="checkout_identification"
  707. * )
  708. *
  709. * @return Response
  710. */
  711. public function checkoutIdentificationAction(Request $request, EntityManagerInterface $em)
  712. {
  713. $coaching = $request->get("coaching");
  714. $partner = null;
  715. if (!$coaching) {
  716. $partnerId = $request->get('partner');
  717. if (!$partnerId) {
  718. return $this->redirectToRoute('default_home');
  719. }
  720. $partner = $em->getRepository(Partner::class)->find($partnerId);
  721. if (!$partner) {
  722. return $this->redirectToRoute('default_home');
  723. }
  724. if ($partner->getIspractitioner() && !$partner->isSubscriptionActive()) {
  725. return $this->redirectToRoute('default_home');
  726. }
  727. }
  728. $user = new User();
  729. $formRegister = $this->createForm(UserRegisterType::class, $user);
  730. $registerError = null;
  731. $registerSuccess = null;
  732. $account = new Account();
  733. $formLogin = $this->createForm(UserLoginType::class, $account);
  734. $loginError = null;
  735. $handleRegister = $this->handleRegister($formRegister, $user, $request);
  736. if ($handleRegister) {
  737. if ($handleRegister['success']) {
  738. $registerSuccess = "Bienvenue sur ZenDez-Vous ! Votre compte a été créé avec succès. Vous pouvez dès maintenant vous connecter.";
  739. } else {
  740. $registerError = $handleRegister['message'];
  741. }
  742. }
  743. $handleLogin = $this->handleLogin($formLogin, $request);
  744. if ($handleLogin) {
  745. if (!$handleLogin['success']) {
  746. $loginError = $handleLogin['message'];
  747. }
  748. }
  749. if ($_POST && isset($_POST["profile"])) {
  750. $profileId = $_POST["profile"];
  751. if (!$profileId) {
  752. $profileId = "self";
  753. }
  754. if ($coaching) {
  755. $lat = null;
  756. if (isset($_POST['lat'])) { $lat = $_POST['lat']; }
  757. $lon = null;
  758. if (isset($_POST['lon'])) { $on = $_POST['lon']; }
  759. $partners = $em->getRepository(Partner::class)->searchCoachsForUser($this->getUser()->getUser(), $lat, $lon);
  760. $availablesSlots = [];
  761. foreach ($partners as $p) {
  762. $slots = $this->agendaUtils->getAvailableSlots($p[0], null, null, null, true);
  763. foreach ($slots as $slot) {
  764. $availablesSlots[] = [
  765. "start" => $slot->getStartDate(),
  766. "partner" => $p[0],
  767. ];
  768. }
  769. }
  770. usort($availablesSlots, function ($item1, $item2) {
  771. return $item1['start'] <=> $item2['start'];
  772. });
  773. $partner = $availablesSlots[0]["partner"];
  774. }
  775. $sessionParam = $request->get('session');
  776. if ($sessionParam) {
  777. return $this->redirectToRoute("default_checkout_payment", [
  778. "partner" => $partner->getId(),
  779. "profile" => $profileId,
  780. "session" => $sessionParam
  781. ]);
  782. }
  783. return $this->redirectToRoute("default_checkout_choice", [
  784. "partner" => $partner->getId(),
  785. "profile" => $profileId,
  786. "coaching" => $coaching
  787. ]);
  788. }
  789. $criticalError = null;
  790. $slot = null;
  791. return $this->render('checkout/step1.html.twig', [
  792. "partner" => $partner,
  793. "slot" => $slot,
  794. "criticalError" => $criticalError,
  795. "formRegister" => $formRegister->createView(),
  796. "registerError" => $registerError,
  797. "registerSuccess" => $registerSuccess,
  798. "formLogin" => $formLogin->createView(),
  799. "loginError" => $loginError,
  800. "coaching" => $coaching
  801. ]);
  802. }
  803. /**
  804. * @Route(
  805. * path="/checkout/choice",
  806. * name="checkout_choice"
  807. * )
  808. *
  809. * @return Response
  810. */
  811. public function checkoutChoiceAction(Request $request, EntityManagerInterface $em)
  812. {
  813. $isCoaching = (bool)$request->get("coaching");
  814. $partnerId = $request->get('partner');
  815. if (!$partnerId) {
  816. return $this->redirectToRoute('default_home');
  817. }
  818. if (!$this->getUser()) {
  819. return $this->redirectToRoute('default_checkout_identification', ["partner" => $partnerId]);
  820. }
  821. $partner = $em->getRepository(Partner::class)->find($partnerId);
  822. if (!$partner) {
  823. return $this->redirectToRoute('default_home');
  824. }
  825. if (!$isCoaching && $partner->getIspractitioner() && !$partner->getIsActivePractitioner()) {
  826. return $this->redirectToRoute('default_home');
  827. }
  828. $profileParam = $request->get('profile');
  829. $profile = null;
  830. if ($profileParam != "self") {
  831. $profileObj = $this->em->getRepository(CustomerProfile::class)->find($profileParam);
  832. if ($profileObj && $this->getUser()->getUser()->getCustomerProfile()->contains($profileObj)) {
  833. $profile = $profileObj;
  834. }
  835. }
  836. $removeOnlyFirst = false;
  837. $removeOnlySecond = false;
  838. if ($isCoaching) {
  839. if ($this->getUser() && $this->getUser()->getUser()->isFirstCoaching($em)) {
  840. $removeOnlySecond = true;
  841. } else {
  842. $removeOnlyFirst = true;
  843. }
  844. } else {
  845. if ($profile) {
  846. if ($profile->isFirstReservation($partner, $em)) {
  847. $removeOnlySecond = true;
  848. } else {
  849. $removeOnlyFirst = true;
  850. }
  851. } else {
  852. if ($this->getUser() && $this->getUser()->getUser()->isFirstReservation($partner, $em)) {
  853. $removeOnlySecond = true;
  854. } else {
  855. $removeOnlyFirst = true;
  856. }
  857. }
  858. }
  859. // Sélectioner de la prestation
  860. $form = $this->createFormBuilder()
  861. ->add('service', EntityType::class, [
  862. 'class' => ServiceDelivery::class,
  863. 'label' => "Sélectionnez la prestation",
  864. 'choices' => $partner->getAvailablesServicesDeliveries($isCoaching, !$isCoaching, $removeOnlyFirst, $removeOnlySecond),
  865. 'choice_label' => function (?ServiceDelivery $serviceDelivery) {
  866. $duration = $serviceDelivery->getDuration();
  867. if ($duration == 60) { $duration = "1h"; }
  868. elseif ($duration == 90) { $duration = "1h30"; }
  869. else { $duration = $duration.' mn'; }
  870. return $serviceDelivery->getLabel().' ('.$duration.')';
  871. },
  872. "required" => true,
  873. 'constraints' => array(
  874. new NotBlank(["message" => "Veuillez indiquer une prestation"])
  875. )
  876. ])
  877. ->add('canal', EntityType::class, [
  878. 'class' => ServiceDeliveryPlace::class,
  879. 'label' => "Sélectionnez le lieu",
  880. 'choice_label' => 'label',
  881. "required" => true,
  882. "attr" => ["autocomplete" => "off"],
  883. 'constraints' => array(
  884. new NotBlank(["message" => "Veuillez indiquer un lieu"])
  885. )
  886. ])
  887. ->getForm();
  888. if ($partner->getSecondaryLocations() && count($partner->getSecondaryLocations()) > 0) {
  889. $locationOptions = [];
  890. $locationOptions[($partner->getAddressName() ?? "Cabinet principal")." (".$partner->getAddress().", ".$partner->getCity().")"] = 0;
  891. foreach ($partner->getSecondaryLocations() as $secondaryLocation) {
  892. if ($secondaryLocation->getActive()) {
  893. $locationOptions[$secondaryLocation->getName()." (".$secondaryLocation->getAddress().", ".$secondaryLocation->getCity().")"] = $secondaryLocation->getId();
  894. }
  895. }
  896. $form->add('location', ChoiceType::class, [
  897. "label" => "Sélectionnez le cabinet",
  898. "choices" => $locationOptions,
  899. "data" => 0,
  900. "placeholder" => false,
  901. "required" => false,
  902. ]);
  903. }
  904. $form->handleRequest($request);
  905. $service = null;
  906. $canal = null;
  907. $slot = null;
  908. $location = null;
  909. if ($form->isSubmitted() && $form->isValid()) {
  910. $service = $form->get('service')->getData();
  911. $canal = $form->get('canal')->getData();
  912. if ($form->has('location')) {
  913. $locationId = $form->get('location')->getData();
  914. if ($locationId > 0) {
  915. $secondarionLocation = $em->getRepository(PartnerSecondaryLocation::class)->find($locationId);
  916. if ($secondarionLocation && $secondarionLocation->getPartner()->getId() == $partner->getId()) {
  917. $location = $secondarionLocation;
  918. }
  919. }
  920. }
  921. }
  922. $groupSessions = [];
  923. if (!$service) {
  924. $groupSessions = $em->getRepository(GroupSession::class)->findAvailablesByPartner($partner);
  925. }
  926. return $this->render('checkout/step2.html.twig', [
  927. "partner" => $partner,
  928. "slot" => $slot,
  929. "form" => $form->createView(),
  930. "sessions" => $groupSessions,
  931. "service" => $service,
  932. "canal" => $canal,
  933. "location" => $location,
  934. "profile" => $profileParam,
  935. "coaching" => $request->get("coaching"),
  936. ]);
  937. }
  938. /**
  939. * @Route(
  940. * path="/checkout/payment",
  941. * name="checkout_payment"
  942. * )
  943. *
  944. * @return Response
  945. */
  946. public function checkoutPaymentAction(Request $request, EntityManagerInterface $em)
  947. {
  948. $partnerId = $request->get('partner');
  949. if (!$partnerId) {
  950. return $this->redirectToRoute('default_home');
  951. }
  952. if (!$this->getUser()) {
  953. return $this->redirectToRoute('default_checkout_identification', ["partner" => $partnerId]);
  954. }
  955. $partner = $em->getRepository(Partner::class)->find($partnerId);
  956. if (!$partner) {
  957. return $this->redirectToRoute('default_home');
  958. }
  959. if ($partner->getIspractitioner() && !$partner->isSubscriptionActive()) {
  960. return $this->redirectToRoute('default_home');
  961. }
  962. if (!$this->getUser()) {
  963. return $this->redirectToRoute('default_home');
  964. }
  965. $criticalError = null;
  966. $serviceParam = $request->get('service');
  967. $service = null;
  968. if ($serviceParam) {
  969. $service = $this->em->getRepository(ServiceDelivery::class)->find($serviceParam);
  970. if (!$service || $service->getPartner()->getId() != $partner->getId() || !$service->getEnabled() || $service->getHistoryService()) {
  971. $criticalError = "La prestation sélectionnée n'est plus disponible.";
  972. }
  973. }
  974. $canalParam = $request->get('canal');
  975. $canal = null;
  976. if ($canalParam) {
  977. $canal = $this->em->getRepository(ServiceDeliveryPlace::class)->find($canalParam);
  978. if (!$canal || ($service && !$service->getServiceDeliveryPlace()->contains($canal))) {
  979. $criticalError = "Le lieu sélectionné est invalide.";
  980. }
  981. }
  982. $locationParam = $request->get('location');
  983. $secondaryLocation = null;
  984. if ($locationParam) {
  985. $secondaryLocation = $this->em->getRepository(PartnerSecondaryLocation::class)->find($locationParam);
  986. if (!$secondaryLocation || $secondaryLocation->getPartner()->getId() != $partner->getId()) {
  987. $criticalError = "Le cabinet sélectionné est invalide.";
  988. }
  989. }
  990. $sessionParam = $request->get('session');
  991. $session = null;
  992. if ($sessionParam) {
  993. $session = $this->em->getRepository(GroupSession::class)->find($sessionParam);
  994. if (!$session || $session->getPartner()->getId() != $partner->getId() || !$session->isReservable()) {
  995. $criticalError = "La session de groupe n'est plus disponible";
  996. }
  997. }
  998. $slotParam = $request->get('slot');
  999. $slot = null;
  1000. if ($slotParam) {
  1001. $slot = $this->checkSlot($slotParam, $partner, $service, $canal, $secondaryLocation);
  1002. if (!$slot) {
  1003. $criticalError = "Le créneau sélectionné n'est plus disponible.";
  1004. }
  1005. }
  1006. $profileParam = $request->get('profile');
  1007. $profile = null;
  1008. if ($profileParam != "self") {
  1009. $profile = $this->em->getRepository(CustomerProfile::class)->find($profileParam);
  1010. if (!$profile || !$this->getUser()->getUser()->getCustomerProfile()->contains($profile)) {
  1011. $criticalError = "Le profil sélectionné est invalide.";
  1012. }
  1013. }
  1014. if (!$service && !$canal && !$session) {
  1015. return $this->redirectToRoute('default_checkout_identification', ["partner" => $partnerId]);
  1016. }
  1017. if (!$slot && !$session) {
  1018. return $this->redirectToRoute('default_checkout_choice', ["partner" => $partnerId, "profile" => $profileParam]);
  1019. }
  1020. if ($session) {
  1021. $paymentStrategy = DefaultController::getPaymentStrategyForSession($partner, $session);
  1022. } else {
  1023. $paymentStrategy = DefaultController::getPaymentStrategy($partner, $service, $canal);
  1024. }
  1025. if (isset($_POST['pay'])) {
  1026. $reservation = new Reservation();
  1027. $reservation->setPartner($partner);
  1028. $reservation->setUser($this->getUser()->getUser());
  1029. if ($profile) {
  1030. $reservation->setCustomerProfile($profile);
  1031. }
  1032. $reservation->setCreationDate(new \DateTime('now'));
  1033. if ($session) {
  1034. $reservation->setGroupSession($session);
  1035. $reservation->setStartDate($session->getStartDate());
  1036. $reservation->setEffectiveStartDate($session->getStartDate());
  1037. $reservation->setEndDate($session->getEndDate());
  1038. $reservation->setEffectiveEndDate($session->getEndDate());
  1039. } else {
  1040. $reservation->setServiceDelivery($service);
  1041. $reservation->setServiceDeliveryPlace($canal);
  1042. $reservation->setStartDate($slot->getStartDate());
  1043. $reservation->setEffectiveStartDate($this->agendaUtils->calcEffectiveStartDate($slot, $partner, $service, $canal));
  1044. $reservation->setEndDate($slot->getEndDate());
  1045. $reservation->setEffectiveEndDate($this->agendaUtils->calcEffectiveEndDate($slot, $partner, $service, $canal));
  1046. }
  1047. if ($secondaryLocation) {
  1048. $reservation->setPartnerSecondaryLocation($secondaryLocation);
  1049. }
  1050. $this->em->persist($reservation);
  1051. $this->em->flush();
  1052. AgendaUtils::handlePostReservation($reservation, $this->em, $this->googleCalendarService);
  1053. if ($paymentStrategy["total"] == 0) {
  1054. $reservation->setPaid(true);
  1055. $this->em->flush();
  1056. $this->sendReservationEmails($reservation);
  1057. return $this->redirectToRoute("default_checkout_success", ['reservation' => $reservation->getId()]);
  1058. } else {
  1059. $success_url = $this->generateUrl("default_checkout_success", ['reservation' => $reservation->getId()], UrlGeneratorInterface::ABSOLUTE_URL);
  1060. $cancel_url = $this->generateUrl("default_checkout_error", ['reservation' => $reservation->getId()], UrlGeneratorInterface::ABSOLUTE_URL);
  1061. try {
  1062. $checkout = $this->stripeClient->checkout($reservation, $paymentStrategy, $cancel_url, $success_url);
  1063. if ($checkout && $checkout->url) {
  1064. return $this->redirect($checkout->url);
  1065. }
  1066. } catch (ApiErrorException $e) {
  1067. $criticalError = "Une erreur est survenue lors de l'ouverture du paiement.";
  1068. }
  1069. }
  1070. }
  1071. return $this->render('checkout/step3.html.twig', [
  1072. "partner" => $partner,
  1073. "slot" => $slot,
  1074. "service" => $service,
  1075. "session" => $session,
  1076. "canal" => $canal,
  1077. "location" => $secondaryLocation,
  1078. "criticalError" => $criticalError,
  1079. "paymentStrategy" => $paymentStrategy,
  1080. ]);
  1081. }
  1082. public static function getPaymentStrategy(Partner $partner, ServiceDelivery $service, ServiceDeliveryPlace $canal)
  1083. {
  1084. /*
  1085. * STRATEGIE
  1086. * ---------
  1087. *
  1088. * COACHING:
  1089. * Emprunte du total vers ZV
  1090. * -> ok: Capture du montant total
  1091. * -> annulation > 48h: Annulation du PaymentIntent
  1092. * -> annulation < 48h: Capture de 20€
  1093. *
  1094. * PRATICIEN:
  1095. * Emprunte de 20€ vers le praticien
  1096. * -> ok: Annulation du PaymentIntent
  1097. * -> annulation > 48h: Annulation du PaymentIntent
  1098. * -> annulation < 48h: Capture des 20€ avec 10€ de frais
  1099. *
  1100. */
  1101. $price = $service->getRealPrice();
  1102. $charge = $price < 20 ? $price : 20;
  1103. $fee = $price < 20 ? $price-10 : 10;
  1104. if ($fee < 0) { $fee = 0; }
  1105. if ($price == 0) {
  1106. return [
  1107. "strategy-html" => "Cette consultation est gratuite.<br/>Vous n'avez rien à régler. Vous aurez la possibilité d'annuler sans frais votre rendez-vous, bien que vous vous engagiez à honorer ce rendez-vous.",
  1108. "charge" => 0,
  1109. "pay" => 0,
  1110. "total" => $price,
  1111. "behalf_of" => null,
  1112. "cancelation" => [
  1113. "sup48" => [
  1114. "capture" => 0,
  1115. "fee" => 0,
  1116. "refund" => true,
  1117. ],
  1118. "inf48" => [
  1119. "capture" => 0,
  1120. "fee" => 0,
  1121. "refund" => true,
  1122. ],
  1123. ],
  1124. ];
  1125. }
  1126. if ($service->getIsCoaching()) {
  1127. return [
  1128. "strategy-html" => "Vous payez votre consultation directement auprès de ZenDez-Vous.<br/>Pour réserver votre créneau, vous devez régler la somme de ".$price."€. Vous aurez la possibilité d'annuler sans frais votre rendez-vous au moins 48H avant, au-delà, vous serez tout de même débité de ".$charge."€.",
  1129. "charge" => $price,
  1130. "pay" => 0,
  1131. "total" => $price,
  1132. "behalf_of" => null,
  1133. "cancelation" => [
  1134. "sup48" => [
  1135. "capture" => 0,
  1136. "fee" => 0,
  1137. "refund" => true,
  1138. ],
  1139. "inf48" => [
  1140. "capture" => $charge,
  1141. "fee" => 0,
  1142. "refund" => false,
  1143. ],
  1144. ],
  1145. ];
  1146. } else {
  1147. return [
  1148. "strategy-html" => "Vous payez votre consultation directement auprès du praticien.<br/>Pour réserver votre créneau, vous devez régler une empreinte bancaire de ".$charge."€. Vous ne serez débité que si vous annulez votre rendez-vous moins de 48 heures avant votre rendez-vous ou si vous ne vous présentez pas auprès du praticien, conformément aux <a href='/assets/docs/zendez-vous_cgv.pdf' target='_blank' rel='noreferrer'>conditions générales de vente.</a>",
  1149. "charge" => $charge,
  1150. "pay" => 0,
  1151. "total" => $charge,
  1152. "behalf_of" => $partner->getStripeAccountId(),
  1153. "cancelation" => [
  1154. "sup48" => [
  1155. "capture" => 0,
  1156. "fee" => 0,
  1157. "refund" => true,
  1158. ],
  1159. "inf48" => [
  1160. "capture" => $charge,
  1161. "fee" => $fee,
  1162. "refund" => false,
  1163. ],
  1164. ],
  1165. ];
  1166. }
  1167. }
  1168. public static function getPaymentStrategyForSession(Partner $partner, GroupSession $session)
  1169. {
  1170. $price = $session->getPrice();
  1171. $charge = $price < 20 ? $price : 20;
  1172. $fee = $price < 20 ? $price-10 : 10;
  1173. if ($fee < 0) { $fee = 0; }
  1174. if ($price == 0) {
  1175. return [
  1176. "strategy-html" => "Cette session est gratuite.<br/>Vous n'avez rien à régler. Vous aurez la possibilité d'annuler sans frais, bien que vous vous engagiez à honorer votre présence.",
  1177. "charge" => 0,
  1178. "pay" => 0,
  1179. "total" => $price,
  1180. "behalf_of" => null,
  1181. "cancelation" => [
  1182. "sup48" => [
  1183. "capture" => 0,
  1184. "fee" => 0,
  1185. "refund" => true,
  1186. ],
  1187. "inf48" => [
  1188. "capture" => 0,
  1189. "fee" => 0,
  1190. "refund" => true,
  1191. ],
  1192. ],
  1193. ];
  1194. }
  1195. return [
  1196. "strategy-html" => "Vous payez votre session directement auprès du praticien.<br/>Pour valider votre inscription, vous devez régler une empreinte bancaire de ".$charge."€. Vous ne serez débité que si vous annulez votre rendez-vous moins de 48 heures avant votre rendez-vous ou si vous ne vous présentez pas à la session, conformément aux <a href='/assets/docs/zendez-vous_cgv.pdf' target='_blank' rel='noreferrer'>conditions générales de vente.</a>",
  1197. "charge" => $charge,
  1198. "pay" => 0,
  1199. "total" => $charge,
  1200. "behalf_of" => $partner->getStripeAccountId(),
  1201. "cancelation" => [
  1202. "sup48" => [
  1203. "capture" => 0,
  1204. "fee" => 0,
  1205. "refund" => true,
  1206. ],
  1207. "inf48" => [
  1208. "capture" => $charge,
  1209. "fee" => $fee,
  1210. "refund" => false,
  1211. ],
  1212. ],
  1213. ];
  1214. }
  1215. /**
  1216. * @Route(
  1217. * path="/checkout/success",
  1218. * name="checkout_success"
  1219. * )
  1220. *
  1221. * @return Response
  1222. */
  1223. public function checkoutSuccessAction(Request $request, EntityManagerInterface $em)
  1224. {
  1225. $reservationId = $request->get('reservation');
  1226. if (!$reservationId) {
  1227. throw new BadRequestException();
  1228. }
  1229. $reservation = $this->em->getRepository(Reservation::class)->find($reservationId);
  1230. if (!$reservation) {
  1231. throw new BadRequestException();
  1232. }
  1233. if (!$this->getUser()) {
  1234. throw new BadRequestException();
  1235. }
  1236. if (!$reservation->getUser() || ($reservation->getUser()->getId() != $this->getUser()->getUser()->getId() && $reservation->getStartDate() < new \DateTime('now'))) {
  1237. throw new BadRequestException();
  1238. }
  1239. return $this->render('checkout/step4.html.twig', [
  1240. "reservation" => $reservation,
  1241. "partner" => $reservation->getPartner(),
  1242. "error" => false,
  1243. ]);
  1244. }
  1245. /**
  1246. * @Route(
  1247. * path="/checkout/error",
  1248. * name="checkout_error"
  1249. * )
  1250. *
  1251. * @return Response
  1252. */
  1253. public function checkoutErrorAction(Request $request, EntityManagerInterface $em)
  1254. {
  1255. $reservationId = $request->get('reservation');
  1256. if (!$reservationId) {
  1257. throw new BadRequestException();
  1258. }
  1259. $reservation = $this->em->getRepository(Reservation::class)->find($reservationId);
  1260. if (!$reservation) {
  1261. throw new BadRequestException();
  1262. }
  1263. if ($reservation->getUser()->getId() != $this->getUser()->getUser()->getId() && $reservation->getStartDate() < new \DateTime('now')) {
  1264. throw new BadRequestException();
  1265. }
  1266. return $this->render('checkout/step4.html.twig', [
  1267. "reservation" => $reservation,
  1268. "partner" => $reservation->getPartner(),
  1269. "error" => true,
  1270. ]);
  1271. }
  1272. private function checkSlot($slotParam, Partner $practitioner, ServiceDelivery $service, ServiceDeliveryPlace $canal, ?PartnerSecondaryLocation $secondaryLocation): ?Slot
  1273. {
  1274. $slot = null;
  1275. if ($slotParam) {
  1276. // On vérifie la validité du créneau
  1277. $startDateTime = \DateTime::createFromFormat('d/m/Y H:i:s', $slotParam);
  1278. $slotAvailable = null;
  1279. $availableSlots = $this->agendaUtils->getAvailableSlots($practitioner, $service, $canal, $secondaryLocation, null);
  1280. foreach ($availableSlots as $availableSlot) {
  1281. if ($availableSlot->getStartDate() == $startDateTime) {
  1282. $slotAvailable = $availableSlot;
  1283. break;
  1284. }
  1285. }
  1286. if ($slotAvailable) {
  1287. $slot = $slotAvailable;
  1288. }
  1289. }
  1290. return $slot;
  1291. }
  1292. /**
  1293. * @Route(
  1294. * path="/stripevents",
  1295. * name="stripevents"
  1296. * )
  1297. *
  1298. * @param Request $request
  1299. * @return Response
  1300. */
  1301. public function stripeventsAction(Request $request)
  1302. {
  1303. $payload = @file_get_contents('php://input');
  1304. $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
  1305. $endpoint_secret = $this->getParameter('stripe_event_secret');
  1306. $response = new Response();
  1307. try {
  1308. $event = \Stripe\Webhook::constructEvent(
  1309. $payload, $sig_header, $endpoint_secret
  1310. );
  1311. } catch(\UnexpectedValueException $e) {
  1312. $response->setStatusCode(Response::HTTP_BAD_REQUEST);
  1313. return $response;
  1314. } catch(\Exception $e) {
  1315. $response->setStatusCode(Response::HTTP_BAD_REQUEST);
  1316. return $response;
  1317. }
  1318. if ($event) {
  1319. $event_type = $event->type;
  1320. $data = $event->data->object;
  1321. switch ($event_type) {
  1322. case 'checkout.session.completed':
  1323. $event_key = substr($data->client_reference_id, 0, 5);
  1324. switch ($event_key) {
  1325. case "resv_":
  1326. // Réservation
  1327. $reservation = $this->em->getRepository(Reservation::class)->find(substr($data->client_reference_id, 5));
  1328. if ($reservation) {
  1329. $reservation->setPaid(true);
  1330. $reservation->setStripePaymentId($data->payment_intent);
  1331. $this->em->flush();
  1332. $reservationPayment = new ReservationPayment();
  1333. $reservationPayment->setCreationDate(new \DateTime('now'));
  1334. $reservationPayment->setTotalAmount($data->amount_total);
  1335. $reservationPayment->setStripePi($data->payment_intent);
  1336. try {
  1337. $pi = $this->stripeClient->getPaymentIntent($data->payment_intent);
  1338. $reservationPayment->setChargedAmount($pi->amount_capturable);
  1339. $reservationPayment->setPaidAmount($pi->amount_received);
  1340. } catch (ApiErrorException $e) {
  1341. }
  1342. $this->em->persist($reservationPayment);
  1343. $this->em->flush();
  1344. $reservation->setReservationPayment($reservationPayment);
  1345. $this->em->flush();
  1346. $paymentLog = new ReservationPaymentLog();
  1347. $paymentLog->setCreationDate(new \DateTime('now'));
  1348. $paymentLog->setReservationPayment($reservationPayment);
  1349. $paymentLog->setLog("Reception du paiement - Montant:".$data->amount_total);
  1350. $paymentLog->setStripeId($event->id);
  1351. $this->em->persist($paymentLog);
  1352. $this->em->flush();
  1353. $this->sendReservationEmails($reservation);
  1354. $response->setStatusCode(Response::HTTP_OK);
  1355. return $response;
  1356. }
  1357. case "sub1_":
  1358. case "sub2_":
  1359. // Abonnement praticien
  1360. // 1 = montly ; 2 = yearly
  1361. $partner = $this->em->getRepository(Partner::class)->find(substr($data->client_reference_id, 5));
  1362. if ($partner) {
  1363. $subscription = new PartnerSubscription();
  1364. $subscription->setPartner($partner);
  1365. $subscription->setPrice((($data->amount_total)/100)/1.2);
  1366. $subscription->setStartDate(new \DateTime('now'));
  1367. $subscription->setStripeSubId($data->subscription);
  1368. $subscription->setDuration($event_key == "sub1_" ? 1 : 12);
  1369. $subscription->setPaymentType("stripe");
  1370. $this->em->persist($subscription);
  1371. $this->em->flush();
  1372. $partner->setSubscriptionDiscount(null);
  1373. $partner->setSubscriptionDiscountType(null);
  1374. $partner->setSubscriptionDiscountDuration(null);
  1375. $this->em->flush();
  1376. // Notification praticien
  1377. $this->mailjetEmailService->send(
  1378. "ZenDez-Vous: votre abonnement est maintenant actif !",
  1379. $partner->getAccount()->getEmail(),
  1380. 4686131,
  1381. [
  1382. "title" => "Vous êtes abonné à ZenDez-Vous",
  1383. "content" => "Bonjour ".$partner->getFirstName(). " " . $partner->getLastName() .",<br/><br/>Vous venez de souscrire à ZenDez-Vous.<br/>La mise en ligne de votre profil sera effective après configuration de votre compte auprès de notre prestataire Stripe pour recevoir les indemnités. N'oubliez pas de réaliser cette configuration sur votre <a href='https://zendez-vous.fr/manager/login'>intranet</a> si ce n'est pas encore fait.<br/><br/>Merci pour votre confiance !",
  1384. "email" => $partner->getAccount()->getEmail(),
  1385. ]);
  1386. // Notification admin
  1387. $this->mailjetEmailService->send(
  1388. "ZenDez-Vous: ".$partner->getFirstName(). " " . $partner->getLastName()." s'est abonné !",
  1389. "inscription-praticien@zendez-vous.fr",
  1390. 4686131,
  1391. [
  1392. "title" => "Un nouvel abonné sur ZenDez-Vous",
  1393. "content" => "Bonjour, <br/><br/>Le praticien ".$partner->getFirstName(). " " . $partner->getLastName() ." a souscrit à ZenDez-Vous.<br/>",
  1394. "email" => "inscription-praticien@zendez-vous.fr",
  1395. ]);
  1396. $response->setStatusCode(Response::HTTP_OK);
  1397. return $response;
  1398. }
  1399. break;
  1400. default:
  1401. break;
  1402. }
  1403. break;
  1404. case 'subscription_schedule.canceled':
  1405. case 'customer.subscription.deleted':
  1406. $subscription = $this->em->getRepository(PartnerSubscription::class)->findOneBy(["stripeSubId" => $data->id]);
  1407. if ($subscription) {
  1408. $subscription->setCanceled(true);
  1409. $endDate = new \DateTime();
  1410. $endDate->setTimestamp($data->cancel_at);
  1411. $subscription->setEndDate($endDate);
  1412. $this->em->flush();
  1413. // Notification praticien
  1414. $partner = $subscription->getPartner();
  1415. $this->mailjetEmailService->send(
  1416. "ZenDez-Vous: votre abonnement a été résilié !",
  1417. $partner->getAccount()->getEmail(),
  1418. 4686131,
  1419. [
  1420. "title" => "Vous avez résilié votre abonnement",
  1421. "content" => "Bonjour ".$partner->getFirstName()."<br/><br/>Votre abonnement à ZenDez-Vous a été résilié.<br/>A l'échéance, vous ne pourrez plus profiter des fonctionnalités de la plateforme et votre profil ne sera plus visible sur notre site.",
  1422. "email" => $partner->getAccount()->getEmail(),
  1423. ]);
  1424. // Notification admin
  1425. $this->mailjetEmailService->send(
  1426. "ZenDez-Vous: ".$partner->getFirstName(). " " . $partner->getLastName()." a résilié son abonnement...",
  1427. "inscription-praticien@zendez-vous.fr",
  1428. 4686131,
  1429. [
  1430. "title" => "Un praticien a résilié son abonnement",
  1431. "content" => "Bonjour, <br/><br/>Le praticien ".$partner->getFirstName(). " " . $partner->getLastName() ." a résilié son abonnement à ZenDez-Vous.<br/>",
  1432. "email" => "inscription-praticien@zendez-vous.fr",
  1433. ]);
  1434. $response->setStatusCode(Response::HTTP_OK);
  1435. return $response;
  1436. }
  1437. $response->setStatusCode(Response::HTTP_OK);
  1438. return $response;
  1439. break;
  1440. default:
  1441. break;
  1442. }
  1443. }
  1444. $response->setStatusCode(Response::HTTP_BAD_REQUEST);
  1445. return $response;
  1446. }
  1447. private function sendReservationEmails(Reservation $reservation)
  1448. {
  1449. $user = $reservation->getUser();
  1450. $partner = $reservation->getPartner();
  1451. $motif = $reservation->getMotif();
  1452. if ($user && $partner) {
  1453. // Mail client
  1454. $pour = "";
  1455. if ($reservation->getCustomerProfile()) {
  1456. $pour = " pour ".$reservation->getCustomerProfile()->getFirstName()." ".$reservation->getCustomerProfile()->getLastName();
  1457. }
  1458. $this->mailjetEmailService->send(
  1459. "ZenDez-Vous: Votre réservation est confirmée",
  1460. $user->getAccount()->getEmail(),
  1461. 4686131,
  1462. [
  1463. "title" => "Votre réservation est confirmée",
  1464. "content" => "Bonjour " . $user->getFirstName() . "<br/><br/>Vous avez rendez-vous avec ".$partner->getFirstName()." ".$partner->getLastName()." le ".$reservation->getStartDate()->format("d/m/Y à H:i").$pour.".<br/>Motif de la consultation: ".$motif.".",
  1465. "email" => $user->getAccount()->getEmail(),
  1466. ],
  1467. [
  1468. [
  1469. 'ContentType' => "text/calendar",
  1470. 'Filename' => "cal.ics",
  1471. 'Base64Content' => base64_encode($this->icalService->reservationToIcalUser($reservation))
  1472. ]
  1473. ]);
  1474. // Mail partner
  1475. $this->mailjetEmailService->send(
  1476. "ZenDez-Vous: Nouvelle réservation",
  1477. $partner->getAccount()->getEmail(),
  1478. 4686131,
  1479. [
  1480. "title" => "Vous avez une nouvelle réservation",
  1481. "content" => "Bonjour " . $partner->getFirstName() . "<br/><br/>Vous avez rendez-vous avec ".$user->getFirstName()." ".$user->getLastName()." le ".$reservation->getStartDate()->format("d/m/Y à H:i").".<br/>Motif de la consultation: ".$motif.".",
  1482. "email" => $partner->getAccount()->getEmail(),
  1483. ],
  1484. [
  1485. [
  1486. 'ContentType' => "text/calendar",
  1487. 'Filename' => "cal.ics",
  1488. 'Base64Content' => base64_encode($this->icalService->reservationToIcalPartner($reservation))
  1489. ]
  1490. ]);
  1491. }
  1492. }
  1493. /**
  1494. * @Route(
  1495. * path="/stripeventsconnect",
  1496. * name="stripeventsconnect"
  1497. * )
  1498. *
  1499. * @param Request $request
  1500. * @return Response
  1501. */
  1502. public function stripeventsconnectAction(Request $request)
  1503. {
  1504. $payload = @file_get_contents('php://input');
  1505. $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
  1506. $endpoint_secret = $this->getParameter('stripe_event_secret_connect');
  1507. $response = new Response();
  1508. try {
  1509. $event = \Stripe\Webhook::constructEvent(
  1510. $payload, $sig_header, $endpoint_secret
  1511. );
  1512. } catch(\UnexpectedValueException $e) {
  1513. $response->setStatusCode(Response::HTTP_BAD_REQUEST);
  1514. return $response;
  1515. } catch(\Exception $e) {
  1516. $response->setStatusCode(Response::HTTP_BAD_REQUEST);
  1517. return $response;
  1518. }
  1519. if ($event) {
  1520. $event_type = $event->type;
  1521. $data = $event->data->object;
  1522. switch ($event_type) {
  1523. case "account.updated":
  1524. $partner = $this->em->getRepository(Partner::class)->findOneBy(['stripeAccountId' => $data->id]);
  1525. if ($partner) {
  1526. $partner->setStripeAccountVerified($data->details_submitted);
  1527. $partner->setStripeChargesEnabled($data->charges_enabled);
  1528. $partner->setStripePayoutsEnabled($data->payouts_enabled);
  1529. $this->em->flush();
  1530. $response->setStatusCode(Response::HTTP_OK);
  1531. return $response;
  1532. }
  1533. break;
  1534. default:
  1535. break;
  1536. }
  1537. }
  1538. $response->setStatusCode(Response::HTTP_BAD_REQUEST);
  1539. return $response;
  1540. }
  1541. /**
  1542. * @Route(
  1543. * path="/test-gc",
  1544. * name="test_google_calendar"
  1545. * )
  1546. *
  1547. * @return Response
  1548. */
  1549. public function testGoogleCalendar(Request $request, EntityManagerInterface $em, GoogleCalendarService $googleCalendarService)
  1550. {
  1551. throw new NotFoundHttpException();
  1552. $partner = $em->getRepository(Partner::class)->find(76);
  1553. $checkSynchro = $googleCalendarService->check_synchro($partner);
  1554. if ($checkSynchro["success"] == true) {
  1555. $events = $googleCalendarService->getEvents($partner);
  1556. foreach ($events as $event) {
  1557. var_dump($event);
  1558. echo "</br>";
  1559. $transparency = $event->getTransparency();
  1560. if ($transparency == "transparent") {
  1561. echo "L'utilisateur a indiqué être libre pendant cet événement.\n";
  1562. } else {
  1563. echo "L'utilisateur est occupé pendant cet événement.\n";
  1564. }
  1565. echo "</br>";
  1566. echo $event['summary'];
  1567. echo "</br>";
  1568. echo $event['status'];
  1569. echo "</br>";
  1570. var_dump($event['updated']);
  1571. echo "</br>";
  1572. var_dump($event['start']);
  1573. echo "</br>";
  1574. var_dump($event['end']);
  1575. echo "</br>";
  1576. echo $event['description'];
  1577. echo "</br>";
  1578. echo $event['id'];
  1579. echo "</br>";
  1580. echo "</br>";
  1581. }
  1582. return new Response("Connected.");
  1583. } else {
  1584. return new Response("<a href='".$checkSynchro["url"]."'>Connexion</a>");
  1585. }
  1586. }
  1587. /**
  1588. * @Route(
  1589. * path="/test-gc-callback",
  1590. * name="test_google_calendar_callback"
  1591. * )
  1592. *
  1593. * @return Response
  1594. */
  1595. public function testGoogleCalendarCallback(Request $request, EntityManagerInterface $em, GoogleCalendarService $googleCalendarService)
  1596. {
  1597. throw new NotFoundHttpException();
  1598. $partner = $em->getRepository(Partner::class)->find(1);
  1599. $code = $request->get("code");
  1600. if ($code) {
  1601. $googleCalendarService->callback($partner, $code);
  1602. }
  1603. return $this->redirectToRoute('default_test_google_calendar');
  1604. }
  1605. /**
  1606. * @Route(
  1607. * path="/test-ics",
  1608. * name="test_ics"
  1609. * )
  1610. *
  1611. * @return Response
  1612. */
  1613. public function testIcs(Request $request, EntityManagerInterface $em)
  1614. {
  1615. throw new NotFoundHttpException();
  1616. $url = "https://calendar.google.com/calendar/ical/dcrbernard.pro%40gmail.com/private-bed1dbf12461e28136808d40a5ffc0ad/basic.ics";
  1617. $ical = new ICal($url);
  1618. var_dump($ical);
  1619. echo "</br>";
  1620. echo "</br>";
  1621. echo $ical->eventCount;
  1622. echo "</br>";
  1623. echo "</br>";
  1624. $events = $ical->eventsFromInterval('1 month');
  1625. foreach ($events as $event) {
  1626. var_dump($event);
  1627. echo "</br>";
  1628. echo $event->summary;
  1629. echo "</br>";
  1630. echo $event->status;
  1631. echo "</br>";
  1632. echo $event->uid;
  1633. echo "</br>";
  1634. echo $event->dtstart_tz;
  1635. echo "</br>";
  1636. echo $event->printData();
  1637. }
  1638. echo "</br>";
  1639. echo "</br>";
  1640. return new Response("OK");
  1641. }
  1642. /**
  1643. * @Route(
  1644. * path="/migration/coaching",
  1645. * name="migration_coaching"
  1646. * )
  1647. *
  1648. * @return Response
  1649. */
  1650. public function migrationCoaching(Request $request, EntityManagerInterface $em)
  1651. {
  1652. throw new NotFoundHttpException();
  1653. $coachings = $em->getRepository(ServiceDelivery::class)->findBy(["isCoaching" => true]);
  1654. foreach ($coachings as $coaching) {
  1655. if ($coaching->getLabel() == "Conseil ZenDez-Vous 1ère consultation" || $coaching->getLabel() == "Coaching ZenDez-Vous 1ère consultation") {
  1656. $newServiceDelivery = clone $coaching;
  1657. $newServiceDelivery->setLabel("Coaching ZenDez-Vous 1ère consultation");
  1658. $newServiceDelivery->setDuration(30);
  1659. $newServiceDelivery->setPrice(34);
  1660. $this->em->persist($newServiceDelivery);
  1661. $this->em->flush();
  1662. $coaching->setHistoryService($newServiceDelivery);
  1663. $this->em->flush();
  1664. }
  1665. if ($coaching->getLabel() == "Conseil ZenDez-Vous suivi" || $coaching->getLabel() == "Coaching ZenDez-Vous suivi") {
  1666. $newServiceDelivery = clone $coaching;
  1667. $newServiceDelivery->setLabel("Coaching ZenDez-Vous suivi");
  1668. $newServiceDelivery->setDuration(30);
  1669. $newServiceDelivery->setPrice(34);
  1670. $this->em->persist($newServiceDelivery);
  1671. $this->em->flush();
  1672. $coaching->setHistoryService($newServiceDelivery);
  1673. $this->em->flush();
  1674. }
  1675. }
  1676. return new Response("OK");
  1677. }
  1678. }