PaymentController.php 11.7 KB
Newer Older
1 2 3 4 5 6 7 8
<?php

namespace App\Controller;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
9
use Symfony\Component\HttpFoundation\Response;
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Translation\TranslatorInterface;
use Payum\Core\Payum;
use Payum\Core\Request\Notify;
use Payum\Core\Request\GetHumanStatus;
use App\Entity\Flux;
use App\Entity\Payment;
use App\Entity\User;
use App\Entity\GlobalParameter;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use App\Security\LoginAuthenticator;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;

/**
 * Gestion des paiements avec Payum
 */
class PaymentController extends AbstractController
{
    protected $em;
    protected $translator;
    protected $payum;
    protected $authenticator;
    protected $guardHandler;

    public function __construct(EntityManagerInterface $em,
                                TranslatorInterface $translator,
                                LoginAuthenticator $authenticator,
                                GuardAuthenticatorHandler $guardHandler,
                                Payum $payum)
    {
        $this->em = $em;
        $this->translator = $translator;
        $this->payum = $payum;
        $this->authenticator = $authenticator;
        $this->guardHandler = $guardHandler;
    }

    /**
     * Crée une instance de Payment, les tokens associés, et redirige vers la page de paiement
     */
    public function preparePaymentAction(Form $form, $type, $extra_data = null)
    {
        // Enregistre les données du Flux en json, pour l'enregistrer une fois le paiement validé
        $serializer = $this->container->get('serializer');
        $toSerialize = Payment::TYPE_ADHESION == $type ? $form->get('cotisation')->getData() : $form->getData();
        $data = $serializer->normalize($toSerialize,
            null,
            [AbstractNormalizer::ATTRIBUTES => [
                'reference',
                'moyen',
                'montant',
                'role',
                'don' => [
                    'reference',
                    'moyen',
                    'montant',
                    'role',
                    'type',
                    'expediteur' => ['id'],
                    'destinataire' => ['id'],
                    'operateur' => ['id'],
                ],
                'expediteur' => ['id'],
                'destinataire' => ['id'],
                'operateur' => ['id']]
            ]);

        $jsondata = $serializer->serialize($data, 'json');

        // Prepare CB Payment
        if ($this->em->getRepository(GlobalParameter::class)->val(GlobalParameter::USE_PAYZEN) === 'true') {
          $gatewayName = 'payzen';
        } else {
          $this->addFlash(
            'error',
            $this->translator->trans('Une erreur est survenue due à la configuration du paiement dans l\'application. Il est pour l\'instant impossible de payer par CB, merci de contacter votre monnaie locale.')
          );
          return $this->redirectToRoute('index');
        }

        $storage = $this->payum->getStorage('App\Entity\Payment');

        $payment = $storage->create();
        $payment->setNumber(uniqid());
        $payment->setCurrencyCode('978');
        $payment->setDescription($type);
        $payment->setFluxData($jsondata);

        // Data to persist when payment is valid (other than Flux data)
        if (null != $extra_data) {
            $payment->setExtraData($extra_data);
        }

        if ($type == Payment::TYPE_ADHESION) {
            $payment->setTotalAmount($form->get('cotisation')->get('montant')->getData()*100); // 1.23 EUR
            $payment->setClientId('Nouvel adhérent');
            $payment->setClientEmail($form->get('user')->get('email')->getData());
        } else {
            if ($form->has('don') && $form->get('don')->getData()->getMontant() > 0) {
                $payment->setTotalAmount(($form->get('montant')->getData() * 100) + ($form->get('don')->getData()->getMontant() * 100)); // 1.23 EUR
            } else {
                $payment->setTotalAmount($form->get('montant')->getData() * 100); // 1.23 EUR
            }

            $payment->setClientId($this->getUser()->getId());
            $payment->setClientEmail($this->getUser()->getEmail());
        }

Damien Moulard committed
118 119 120 121 122 123 124
        if ($type == Payment::TYPE_PAIEMENT_RECURRENT_COTISATION_TAV) {
          $payment->setRecurrenceAmount($form->get('montant')->getData() * 100);
          $payment->setIsRecurrent(true);
          $payment->setRecurrenceMonthsCount($form->get('nombreMois')->getData());
          $payment->setRecurrenceMonthDay($form->get('jourPrelevement')->getData());
        }

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
        $storage->update($payment);

        $captureToken = $this->payum->getTokenFactory()->createCaptureToken(
          $gatewayName,
          $payment,
          'payment_done' // the route to redirect after capture
        );

        // Symfony creates URLs with http and not https -> replace
        $targetUrl = preg_replace('/^http:/', 'https:', $captureToken->getTargetUrl());
        $afterUrl = preg_replace('/^http:/', 'https:', $captureToken->getAfterUrl());

        $captureToken->setTargetUrl($targetUrl);
        $captureToken->setAfterUrl($afterUrl);

        $this->em->persist($captureToken);
        $this->em->flush();

        return $this->redirect($captureToken->getTargetUrl());
    }

    /**
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
     *
     * Traitement des notifications Payzen.
     * @see doneAction
     *
     * @param Request $request
     * @return Response
     * @Route("/payment/notify/", name="payment_notify")
     */
    public function notifyAction(Request $request)
    {
        //TODO Security possible issue : someone else than Payzen could get the token and submit
        //TODO a recurring notification payment occurence here, which will be accepted...
        //TODO until blocked if user has already payed its cotisation...
        //TODO but if e.g. recurring payment time range is over, the kohinos
        //TODO would not realize it and will continue to trigger new payments on the system
        //TODO clean solution could be to detect last occurence of recurring payment
        //TODO and invalidate token at this point.

        try {
            $token = $this->payum->getHttpRequestVerifier()->verify($request);
        } catch (\Exception  $e) {
            // Token expired
            return new Response("NotifyAction controller : jeton déjà invalidé.");
        }
        // Get payment
        $gateway = $this->payum->getGateway($token->getGatewayName());
        $gateway->execute($status = new GetHumanStatus($token));
        $payment = $status->getFirstModel();
        if ($payment->getStatus() == GetHumanStatus::STATUS_NEW || $payment->getIsRecurrent()) {
            // No notification arrived at this point or payment is recurrent : execute Notify action
            $gateway->execute(new Notify($token));
            return new Response("NotifyAction controller : notification prise en compte.");
        } else {
            // Invalidate token
            $this->payum->getHttpRequestVerifier()->invalidate($token);
            return new Response("NotifyAction controller : jeton valide invalidé.");
        }
    }

    /**
     * Ce contrôleur est sollicité lorsque Payzen renvoie le cotisant sur l'URL de retour.
     *
     * Avant fin mars 2024, on pouvait croire que ce contrôleur était également sollicité lorsque Payzen notifie du paiement
     * via l'URL de notification, mais ce n'est pas le cas car la configuration du back office Payzen
     * est réécrite par la configuration par défaut du module Payum.
     *
     * C'est donc désormais le contrôleur notifyAction qui est sollicité
     * (ajout de ce contrôleur suite découverte bug lors de la mise en place du paiement récurrent,
     * jusqu'ici les notifications payzen n'étaient donc pas captées, ce qui ne pose pas nécessairement
     * problème car les utilisateurs sont redirigés vers l'URL de retour s'ils ne ferment pas leur navigateur.)
     *
     * Pour plus de clarté, on gagne à modifier finalement l'URL de retour Payzen dans le backoffice de sorte
     * à pointer vers notifyAction également.
200 201 202 203 204 205
     *
     * @Route("/payment/done/", name="payment_done")
     */
    public function doneAction(Request $request)
    {
        try {
206
            $token = $this->payum->getHttpRequestVerifier()->verify($request);
207
        } catch (\Exception  $e) {
208 209
            // Token expired
            return $this->redirectToRoute('index');
210 211 212 213 214 215 216
        }

        // Get payment
        $gateway = $this->payum->getGateway($token->getGatewayName());
        $gateway->execute($status = new GetHumanStatus($token));
        $payment = $status->getFirstModel();

217 218 219 220
        //Done action is normaly only accessed by users browser, not by Payzen agent,
        //so we should not be handling recurring payment after the initial payement.
        //We must indeed prevent a user from a browser reload e.g.

221
        if ($payment->getStatus() == GetHumanStatus::STATUS_NEW) {
222 223 224
            // No notification arrived at this point : execute Notify action
            // Recurring payment should not trigger notify action here except on initial payment
            // as only Payzen agent is allowed to trigger occurences after the first one
225
            $gateway->execute(new Notify($token));
226 227
        } elseif (!$payment->getIsRecurrent()) {
            // Invalidate token, except if payment is recurrent
228
            $this->payum->getHttpRequestVerifier()->invalidate($token);
229 230 231
        } else {
            //maybe user is trying to trigger another occurence of a recurring payment
            return $this->redirectToRoute('index');
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
        }

        // Set flash message according to payment status
        if ($payment->getStatus() == GetHumanStatus::STATUS_CAPTURED || $payment->getStatus() == GetHumanStatus::STATUS_AUTHORIZED) {
          $type = $payment->getDescription();

          if (Payment::TYPE_ACHAT_MONNAIE_ADHERENT == $type || Payment::TYPE_ACHAT_MONNAIE_PRESTA == $type) {
            $this->addFlash(
              'success',
              $this->translator->trans('Achat de monnaie locale bien effectué !')
            );
          } else if (Payment::TYPE_COTISATION_ADHERENT == $type || Payment::TYPE_COTISATION_PRESTA == $type) {
            $this->addFlash(
              'success',
              $this->translator->trans('Cotisation bien reçue. Merci !')
            );
          } else if (Payment::TYPE_ADHESION == $type) {
            $this->addFlash(
              'success',
              $this->translator->trans('Votre adhésion a bien été prise en compte, bienvenue !')
            );

            // Connect new user
            return $this->guardHandler
              ->authenticateUserAndHandleSuccess(
                $this->em->getRepository(User::class)->findOneBy(array('id' => $payment->getClientId())),
                $request,
                $this->authenticator,
                'main'
            );
262
          } else if (Payment::TYPE_PAIEMENT_COTISATION_TAV == $type || Payment::TYPE_PAIEMENT_RECURRENT_COTISATION_TAV)  {
263 264 265 266
            $this->addFlash(
              'success',
              $this->translator->trans('Cotisation payée !')
            );
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
          }
        } else if ($payment->getStatus() == GetHumanStatus::STATUS_CANCELED ||
                    $payment->getStatus() == GetHumanStatus::STATUS_EXPIRED ||
                    $payment->getStatus() == GetHumanStatus::STATUS_FAILED) 
        {
          $this->addFlash(
            'error',
            $this->translator->trans('La transaction a été annulée.')
          );
        }        

        return $this->redirectToRoute('index');
    }

}