Commit 35ebb0c6 by Yvon Kerdoncuff

Merge branch '6715-payment-duplicates' into 'develop'

payment duplicates

See merge request cooperatic/kohinos-tav!118
parents 609fe300 fd0f6d90
...@@ -5,6 +5,7 @@ namespace App\Controller; ...@@ -5,6 +5,7 @@ namespace App\Controller;
use App\Entity\GlobalParameter; use App\Entity\GlobalParameter;
use App\Entity\Payment; use App\Entity\Payment;
use App\Entity\User; use App\Entity\User;
use App\Repository\PaymentRepository;
use App\Security\LoginAuthenticator; use App\Security\LoginAuthenticator;
use App\Utils\PaymentUtils; use App\Utils\PaymentUtils;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
...@@ -32,13 +33,14 @@ class PaymentController extends AbstractController ...@@ -32,13 +33,14 @@ class PaymentController extends AbstractController
protected $guardHandler; protected $guardHandler;
protected $paymentUtils; protected $paymentUtils;
public function __construct(EntityManagerInterface $em, public function __construct(
EntityManagerInterface $em,
TranslatorInterface $translator, TranslatorInterface $translator,
LoginAuthenticator $authenticator, LoginAuthenticator $authenticator,
GuardAuthenticatorHandler $guardHandler, GuardAuthenticatorHandler $guardHandler,
Payum $payum, Payum $payum,
PaymentUtils $paymentUtils) PaymentUtils $paymentUtils
{ ) {
$this->em = $em; $this->em = $em;
$this->translator = $translator; $this->translator = $translator;
$this->payum = $payum; $this->payum = $payum;
...@@ -55,7 +57,8 @@ class PaymentController extends AbstractController ...@@ -55,7 +57,8 @@ class PaymentController extends AbstractController
// Enregistre les données du Flux en json, pour l'enregistrer une fois le paiement validé // Enregistre les données du Flux en json, pour l'enregistrer une fois le paiement validé
$serializer = $this->container->get('serializer'); $serializer = $this->container->get('serializer');
$toSerialize = Payment::TYPE_ADHESION == $type ? $form->get('cotisation')->getData() : $form->getData(); $toSerialize = Payment::TYPE_ADHESION == $type ? $form->get('cotisation')->getData() : $form->getData();
$data = $serializer->normalize($toSerialize, $data = $serializer->normalize(
$toSerialize,
null, null,
[AbstractNormalizer::ATTRIBUTES => [ [AbstractNormalizer::ATTRIBUTES => [
'reference', 'reference',
...@@ -75,7 +78,8 @@ class PaymentController extends AbstractController ...@@ -75,7 +78,8 @@ class PaymentController extends AbstractController
'expediteur' => ['id'], 'expediteur' => ['id'],
'destinataire' => ['id'], 'destinataire' => ['id'],
'operateur' => ['id'], ], 'operateur' => ['id'], ],
]); ]
);
$jsondata = $serializer->serialize($data, 'json'); $jsondata = $serializer->serialize($data, 'json');
...@@ -325,9 +329,11 @@ class PaymentController extends AbstractController ...@@ -325,9 +329,11 @@ class PaymentController extends AbstractController
$this->translator->trans('Cotisation payée !') $this->translator->trans('Cotisation payée !')
); );
} }
} elseif (GetHumanStatus::STATUS_CANCELED == $payment->getStatus() || } elseif (
GetHumanStatus::STATUS_CANCELED == $payment->getStatus() ||
GetHumanStatus::STATUS_EXPIRED == $payment->getStatus() || GetHumanStatus::STATUS_EXPIRED == $payment->getStatus() ||
GetHumanStatus::STATUS_FAILED == $payment->getStatus()) { GetHumanStatus::STATUS_FAILED == $payment->getStatus()
) {
$this->addFlash( $this->addFlash(
'error', 'error',
$this->translator->trans('La transaction a été annulée.') $this->translator->trans('La transaction a été annulée.')
......
...@@ -21,6 +21,12 @@ class Payment extends BasePayment ...@@ -21,6 +21,12 @@ class Payment extends BasePayment
const TYPE_PAIEMENT_COTISATION_TAV = 'paiement_cotisation_tav'; const TYPE_PAIEMENT_COTISATION_TAV = 'paiement_cotisation_tav';
const TYPE_PAIEMENT_RECURRENT_COTISATION_TAV = 'paiement_recurrent_cotisation_tav'; const TYPE_PAIEMENT_RECURRENT_COTISATION_TAV = 'paiement_recurrent_cotisation_tav';
public function __construct()
{
parent::__construct();
$this->created_at = new \DateTime();
}
/** /**
* @var \Ramsey\Uuid\UuidInterface * @var \Ramsey\Uuid\UuidInterface
* *
...@@ -75,6 +81,20 @@ class Payment extends BasePayment ...@@ -75,6 +81,20 @@ class Payment extends BasePayment
private $recurrenceAmount; private $recurrenceAmount;
/** /**
* @var string|null
*
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $startingPaymentAnalysisStatus;
/**
* Payment creation datetime.
*
* @ORM\Column(type="datetime", nullable=true)
*/
private $created_at;
/**
* @return string * @return string
*/ */
public function getStatus(): ?string public function getStatus(): ?string
...@@ -183,6 +203,31 @@ class Payment extends BasePayment ...@@ -183,6 +203,31 @@ class Payment extends BasePayment
} }
/** /**
* @return string
*/
public function getStartingPaymentAnalysisStatus(): ?string
{
return $this->startingPaymentAnalysisStatus;
}
/**
* @param string $startingPaymentAnalysisStatus
*
* @return Payment
*/
public function setStartingPaymentAnalysisStatus(string $startingPaymentAnalysisStatus): self
{
$this->startingPaymentAnalysisStatus = $startingPaymentAnalysisStatus;
return $this;
}
public function getCreatedAt(): ?DateTime
{
return $this->created_at;
}
/**
* Return null in case of error * Return null in case of error
* Returns true if payment is already ended or CB expired * Returns true if payment is already ended or CB expired
* Returns false otherwise * Returns false otherwise
......
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241119090231 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE payment ADD starting_payment_analysis_status VARCHAR(50) DEFAULT NULL, ADD created_at DATETIME DEFAULT NULL');
$this->addSql('ALTER TABLE prestataire CHANGE iban iban LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:personal_data)\'');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE payment DROP starting_payment_analysis_status, DROP created_at');
$this->addSql('ALTER TABLE prestataire CHANGE iban iban LONGTEXT CHARACTER SET utf8mb3 DEFAULT NULL COLLATE `utf8mb3_general_ci` COMMENT \'(DC2Type:personal_data)\'');
}
}
...@@ -5,6 +5,8 @@ namespace App\Repository; ...@@ -5,6 +5,8 @@ namespace App\Repository;
use App\Entity\Payment; use App\Entity\Payment;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
use Payum\Core\Request\GetHumanStatus;
use Symfony\Component\HttpClient\HttpClient;
/** /**
* @method Siege|null find($id, $lockMode = null, $lockVersion = null) * @method Siege|null find($id, $lockMode = null, $lockVersion = null)
...@@ -39,4 +41,41 @@ class PaymentRepository extends ServiceEntityRepository ...@@ -39,4 +41,41 @@ class PaymentRepository extends ServiceEntityRepository
return $results[0]; return $results[0];
} }
} }
/*
* Everytime a user clicks on "Payer en CB" button on the kohinos side,
* a Payment entry is created.
* We want to prevent a user from starting several payments, which can
* lead to duplicates.
* We mark as unvalid Payment that are unvalid when we check them.
*/
public function findValidStartingPayment($clientEmail)
{
//Remark : we don't know how to redirect the user to the existing payment page,
//we simply return the datetime when the payment will be expired.
$candidates = $this->findBy([
'clientEmail' => $clientEmail,
'startingPaymentAnalysisStatus' => null,
]);
foreach ($candidates as $p) {
if (!in_array($p->getStatus(),[null,GetHumanStatus::STATUS_NEW])) {
$p->setStartingPaymentAnalysisStatus('NOT CONCERNED');
continue;
}
$createdAt = clone $p->getCreatedAt(); //don't modify original object
$timeout = $createdAt->add(\DateInterval::createFromDateString("10 minutes"));
if ($timeout < new \DateTime()) {
$p->setStartingPaymentAnalysisStatus('TIMEOUT');
} else {
return $timeout;
}
}
//Note : some fields updates are done in this method to exclude non-candidate payment from future research.
//We may not want to flush here so flushing will probably not occur when an ongoing starting payment is found,
//but it will occur only when the payment process succeeds, which is fine.
//no valid payment found
return null;
}
} }
...@@ -12,6 +12,7 @@ use App\Entity\Flux; ...@@ -12,6 +12,7 @@ use App\Entity\Flux;
use App\Entity\CotisationTavReversement; use App\Entity\CotisationTavReversement;
use App\Entity\CotisationTavPrelevement; use App\Entity\CotisationTavPrelevement;
use App\Enum\MoyenEnum; use App\Enum\MoyenEnum;
use App\Repository\PaymentRepository;
use App\Utils\CustomEntityManager; use App\Utils\CustomEntityManager;
use Payum\Core\Request\GetHumanStatus; use Payum\Core\Request\GetHumanStatus;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
...@@ -38,14 +39,23 @@ class TAVCotisationUtils ...@@ -38,14 +39,23 @@ class TAVCotisationUtils
*/ */
public function preventCotisationDuplication(Adherent $adherent) public function preventCotisationDuplication(Adherent $adherent)
{ {
$email = $adherent->getUser()->getEmail();
//Look for existing recurring payment //Look for existing recurring payment
if($reason = $this->checkExistingRecurringPayment($adherent->getUser()->getEmail())) { if($reason = $this->checkExistingRecurringPayment($email)) {
return implode(" ", array_column($reason,'reason')); return implode(" ", array_column($reason,'reason'));
} }
//Look for existing cotisation //Look for existing cotisation
if ($this->checkExistingCotisation($adherent)) { if ($this->checkExistingCotisation($adherent)) {
return "Cotisation déjà payée ce mois-ci."; return "Cotisation déjà payée ce mois-ci.";
} }
//Look for possible Payzen starting payment (neither finished nor expired yet)
/* @var PaymentRepository $repo */
$repo = $this->em->getRepository(Payment::class);
$foundStartingPaymentTimeout = $repo->findValidStartingPayment($email);
if ($foundStartingPaymentTimeout) {
return "Détection d'un possible paiement déjà en cours. Le paiement sera de nouveau possible à "
. $foundStartingPaymentTimeout->format("H:i:s") . ", horaire d'expiration du paiement en cours.";
}
return ""; return "";
} }
......
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