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;
use App\Entity\GlobalParameter;
use App\Entity\Payment;
use App\Entity\User;
use App\Repository\PaymentRepository;
use App\Security\LoginAuthenticator;
use App\Utils\PaymentUtils;
use Doctrine\ORM\EntityManagerInterface;
......@@ -32,13 +33,14 @@ class PaymentController extends AbstractController
protected $guardHandler;
protected $paymentUtils;
public function __construct(EntityManagerInterface $em,
TranslatorInterface $translator,
LoginAuthenticator $authenticator,
GuardAuthenticatorHandler $guardHandler,
Payum $payum,
PaymentUtils $paymentUtils)
{
public function __construct(
EntityManagerInterface $em,
TranslatorInterface $translator,
LoginAuthenticator $authenticator,
GuardAuthenticatorHandler $guardHandler,
Payum $payum,
PaymentUtils $paymentUtils
) {
$this->em = $em;
$this->translator = $translator;
$this->payum = $payum;
......@@ -55,7 +57,8 @@ class PaymentController extends AbstractController
// 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,
$data = $serializer->normalize(
$toSerialize,
null,
[AbstractNormalizer::ATTRIBUTES => [
'reference',
......@@ -75,7 +78,8 @@ class PaymentController extends AbstractController
'expediteur' => ['id'],
'destinataire' => ['id'],
'operateur' => ['id'], ],
]);
]
);
$jsondata = $serializer->serialize($data, 'json');
......@@ -84,9 +88,9 @@ class PaymentController extends AbstractController
$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.')
);
'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');
}
......@@ -129,9 +133,9 @@ class PaymentController extends AbstractController
$storage->update($payment);
$captureToken = $this->payum->getTokenFactory()->createCaptureToken(
$gatewayName,
$payment,
'payment_done' // the route to redirect after capture
$gatewayName,
$payment,
'payment_done' // the route to redirect after capture
);
// Symfony creates URLs with http and not https -> replace
......@@ -297,41 +301,43 @@ class PaymentController extends AbstractController
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é !')
);
'success',
$this->translator->trans('Achat de monnaie locale bien effectué !')
);
} elseif (Payment::TYPE_COTISATION_ADHERENT == $type || Payment::TYPE_COTISATION_PRESTA == $type) {
$this->addFlash(
'success',
$this->translator->trans('Cotisation bien reçue. Merci !')
);
'success',
$this->translator->trans('Cotisation bien reçue. Merci !')
);
} elseif (Payment::TYPE_ADHESION == $type) {
$this->addFlash(
'success',
$this->translator->trans('Votre adhésion a bien été prise en compte, bienvenue !')
);
'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(['id' => $payment->getClientId()]),
$request,
$this->authenticator,
'main'
);
->authenticateUserAndHandleSuccess(
$this->em->getRepository(User::class)->findOneBy(['id' => $payment->getClientId()]),
$request,
$this->authenticator,
'main'
);
} elseif (Payment::TYPE_PAIEMENT_COTISATION_TAV == $type || Payment::TYPE_PAIEMENT_RECURRENT_COTISATION_TAV) {
$this->addFlash(
'success',
$this->translator->trans('Cotisation payée !')
);
'success',
$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_FAILED == $payment->getStatus()) {
GetHumanStatus::STATUS_FAILED == $payment->getStatus()
) {
$this->addFlash(
'error',
$this->translator->trans('La transaction a été annulée.')
);
'error',
$this->translator->trans('La transaction a été annulée.')
);
}
return $this->redirectToRoute('index');
......
......@@ -21,6 +21,12 @@ class Payment extends BasePayment
const TYPE_PAIEMENT_COTISATION_TAV = 'paiement_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
*
......@@ -75,6 +81,20 @@ class Payment extends BasePayment
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
*/
public function getStatus(): ?string
......@@ -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
* Returns true if payment is already ended or CB expired
* 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;
use App\Entity\Payment;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Payum\Core\Request\GetHumanStatus;
use Symfony\Component\HttpClient\HttpClient;
/**
* @method Siege|null find($id, $lockMode = null, $lockVersion = null)
......@@ -39,4 +41,41 @@ class PaymentRepository extends ServiceEntityRepository
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;
use App\Entity\CotisationTavReversement;
use App\Entity\CotisationTavPrelevement;
use App\Enum\MoyenEnum;
use App\Repository\PaymentRepository;
use App\Utils\CustomEntityManager;
use Payum\Core\Request\GetHumanStatus;
use Symfony\Component\Security\Core\Security;
......@@ -38,14 +39,23 @@ class TAVCotisationUtils
*/
public function preventCotisationDuplication(Adherent $adherent)
{
$email = $adherent->getUser()->getEmail();
//Look for existing recurring payment
if($reason = $this->checkExistingRecurringPayment($adherent->getUser()->getEmail())) {
if($reason = $this->checkExistingRecurringPayment($email)) {
return implode(" ", array_column($reason,'reason'));
}
//Look for existing cotisation
if ($this->checkExistingCotisation($adherent)) {
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 "";
}
......
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