Commit 47a7ad1f by Yvon

force recurring payment date min of current date and 28 + really collect payzen…

force recurring payment date min of current date and 28 + really collect payzen notification + adapt payment logic to the recurring case
parent 9cf0fb7a
...@@ -147,7 +147,9 @@ Une fois tout installé, éventuellement changer le logo et favicon situés dans ...@@ -147,7 +147,9 @@ Une fois tout installé, éventuellement changer le logo et favicon situés dans
Une fois Payzen configuré sur le Kohinos, il faut le configurer sur le Back Office de Payzen : Une fois Payzen configuré sur le Kohinos, il faut le configurer sur le Back Office de Payzen :
Paramétrage -> Boutique : configurer les 2 "URL de retour de la boutique" avec la page d'accueil du site. Paramétrage -> Boutique : configurer les 2 "URL de retour de la boutique" avec la page d'accueil du site.
Paramétrage -> Règles de notification -> URL de notification à la fin du paiement : dans le 2ème encadré, renseigner les 2 champs avec [url_du_site]/payment/done Paramétrage -> Règles de notification -> URL de notification à la fin du paiement : dans le 2ème encadré, renseigner les 2 champs avec [url_du_site]/payment/notify
Paramétrage -> Règles de notification -> URL de notification à la création d'un abonnement (mal nommé à mon avis car utilisé à chaque échéance) : dans le 2ème encadré, renseigner les 2 champs avec [url_du_site]/payment/notify
Remarque : les URLS de notification sont réécrites par Payum à la même valeur dans le cadre de sa configuration par défaut
Une fois l'installation et la configuration du Kohinos terminées, il est conseillé de réaliser un premier paiement afin de tester le bon fonctionnement de la communication avec Payzen en environnement de production. Une fois l'installation et la configuration du Kohinos terminées, il est conseillé de réaliser un premier paiement afin de tester le bon fonctionnement de la communication avec Payzen en environnement de production.
......
...@@ -6,6 +6,7 @@ use Doctrine\ORM\EntityManagerInterface; ...@@ -6,6 +6,7 @@ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Form; use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Translation\TranslatorInterface;
use Payum\Core\Payum; use Payum\Core\Payum;
...@@ -143,17 +144,69 @@ class PaymentController extends AbstractController ...@@ -143,17 +144,69 @@ class PaymentController extends AbstractController
} }
/** /**
* Fonction de retour sur le site par l'utilisateur après paiement *
* 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.
* *
* @Route("/payment/done/", name="payment_done") * @Route("/payment/done/", name="payment_done")
*/ */
public function doneAction(Request $request) public function doneAction(Request $request)
{ {
try { try {
$token = $this->payum->getHttpRequestVerifier()->verify($request); $token = $this->payum->getHttpRequestVerifier()->verify($request);
} catch (\Exception $e) { } catch (\Exception $e) {
// Token expired // Token expired
return $this->redirectToRoute('index'); return $this->redirectToRoute('index');
} }
// Get payment // Get payment
...@@ -161,12 +214,21 @@ class PaymentController extends AbstractController ...@@ -161,12 +214,21 @@ class PaymentController extends AbstractController
$gateway->execute($status = new GetHumanStatus($token)); $gateway->execute($status = new GetHumanStatus($token));
$payment = $status->getFirstModel(); $payment = $status->getFirstModel();
//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.
if ($payment->getStatus() == GetHumanStatus::STATUS_NEW) { if ($payment->getStatus() == GetHumanStatus::STATUS_NEW) {
// No notification arrived at this point: execute Notify action // 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
$gateway->execute(new Notify($token)); $gateway->execute(new Notify($token));
} else { } elseif (!$payment->getIsRecurrent()) {
// Invalidate token // Invalidate token, except if payment is recurrent
$this->payum->getHttpRequestVerifier()->invalidate($token); $this->payum->getHttpRequestVerifier()->invalidate($token);
} else {
//maybe user is trying to trigger another occurence of a recurring payment
return $this->redirectToRoute('index');
} }
// Set flash message according to payment status // Set flash message according to payment status
......
...@@ -96,11 +96,13 @@ class PaymentStatusExtension implements ExtensionInterface ...@@ -96,11 +96,13 @@ class PaymentStatusExtension implements ExtensionInterface
// Get current & new status // Get current & new status
$context->getGateway()->execute($status = new GetHumanStatus($payment)); $context->getGateway()->execute($status = new GetHumanStatus($payment));
$current_payment_status = $payment->getStatus(); $current_payment_status = $payment->getStatus();
$current_payment_is_recurrent = $payment->getIsRecurrent();
// Payment can be captured if it hasn't been captured before // Payment can be captured if it hasn't been captured before
// TODO for recurrent payment: allow STATUS_CAPTURED and STATUS_AUTHORIZED for recurrent payment only. The payment was already captured. // Allow STATUS_CAPTURED and STATUS_AUTHORIZED for recurrent payment only.
if ($current_payment_status !== GetHumanStatus::STATUS_CAPTURED if ($current_payment_status !== GetHumanStatus::STATUS_CAPTURED
&& $current_payment_status != GetHumanStatus::STATUS_AUTHORIZED) && $current_payment_status != GetHumanStatus::STATUS_AUTHORIZED
|| $current_payment_is_recurrent)
{ {
// If payment succesful, persist serialized 'Flux' stored in payment // If payment succesful, persist serialized 'Flux' stored in payment
if ($status->getValue() == GetHumanStatus::STATUS_CAPTURED if ($status->getValue() == GetHumanStatus::STATUS_CAPTURED
...@@ -277,12 +279,15 @@ class PaymentStatusExtension implements ExtensionInterface ...@@ -277,12 +279,15 @@ class PaymentStatusExtension implements ExtensionInterface
$flux->setOperateur($op); $flux->setOperateur($op);
$flux->setReconverti(true); $flux->setReconverti(true);
// TODO for recurrent payment: save don only for first payment, otherwise remove don //For recurrent payment: save don only for first payment, otherwise remove don
if (null != $flux->getDon()) { //Use current payment status to decide (if first payment -> add don, if not -> remove don)
if (null != $flux->getDon() && $current_payment_status === GetHumanStatus::STATUS_NEW) {
$flux->getDon()->setType(Don::TYPE_DON_ADHERENT); $flux->getDon()->setType(Don::TYPE_DON_ADHERENT);
$flux->getDon()->setOperateur($op); $flux->getDon()->setOperateur($op);
$flux->getDon()->setExpediteur($dest); $flux->getDon()->setExpediteur($dest);
$flux->getDon()->setDestinataire($this->em->getRepository(Prestataire::class)->findOneBy(['mlc' => true])); $flux->getDon()->setDestinataire($this->em->getRepository(Prestataire::class)->findOneBy(['mlc' => true]));
} else {
$flux->setDon(null);
} }
} else { } else {
// Bad request // Bad request
...@@ -300,9 +305,11 @@ class PaymentStatusExtension implements ExtensionInterface ...@@ -300,9 +305,11 @@ class PaymentStatusExtension implements ExtensionInterface
} }
} }
// TODO for recurrent payment: don't delete notify token because we'll be notified on next recurrence payments
// Invalidate (delete) notify token after payment is captured // Invalidate (delete) notify token after payment is captured
$this->em->remove($token); // For recurrent payment: don't delete notify token because we'll be notified on next recurrence payments
if(!$current_payment_is_recurrent) {
$this->em->remove($token);
}
$this->em->flush(); $this->em->flush();
} }
......
...@@ -21,6 +21,7 @@ class AchatMonnaieAdherentRecurrentFormType extends AchatMonnaieAdherentFormType ...@@ -21,6 +21,7 @@ class AchatMonnaieAdherentRecurrentFormType extends AchatMonnaieAdherentFormType
for ($i = 1; $i <= 28; $i++) { for ($i = 1; $i <= 28; $i++) {
$jourPrelevementChoices[strval($i)] = $i; $jourPrelevementChoices[strval($i)] = $i;
} }
$now = new \DateTime();
$builder $builder
->add('nombreMois', IntegerType::class, [ ->add('nombreMois', IntegerType::class, [
'label' => 'Nombre d\'échéances désirées : ', 'label' => 'Nombre d\'échéances désirées : ',
...@@ -32,9 +33,10 @@ class AchatMonnaieAdherentRecurrentFormType extends AchatMonnaieAdherentFormType ...@@ -32,9 +33,10 @@ class AchatMonnaieAdherentRecurrentFormType extends AchatMonnaieAdherentFormType
->add('jourPrelevement', ChoiceType::class, [ ->add('jourPrelevement', ChoiceType::class, [
'label' => 'Jour du prélèvement dans le mois : ', 'label' => 'Jour du prélèvement dans le mois : ',
'choices' => $jourPrelevementChoices, 'choices' => $jourPrelevementChoices,
'data' => min($now->format('d'),28),
'required' => true, 'required' => true,
'mapped' => false, 'mapped' => false,
'attr' => ['autocomplete' => 'off'] 'attr' => ['autocomplete' => 'off', 'disabled' => true]
]) ])
->remove('saveHelloAsso') ->remove('saveHelloAsso')
->remove('save') ->remove('save')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment