- contact@e-cosplay.fr remplace par client@e-cosplay.fr dans 87 fichiers (PDFs, templates, emails, controllers, DocuSeal submitters) - monitor@e-cosplay.fr remplace par notification@e-cosplay.fr dans 4 fichiers (webhooks DocuSeal, commandes DNS/NDD, controller echeancier) - Ajout lien "En savoir plus sur notre association" vers www.e-cosplay.fr sur la page migration SITECONSEIL Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
668 lines
26 KiB
PHP
668 lines
26 KiB
PHP
<?php
|
|
|
|
namespace App\Controller\Admin;
|
|
|
|
use App\Entity\Advert;
|
|
use App\Entity\AdvertPayment;
|
|
use App\Entity\Customer;
|
|
use App\Entity\Echeancier;
|
|
use App\Entity\EcheancierLine;
|
|
use App\Service\DocuSealService;
|
|
use App\Service\MailerService;
|
|
use App\Service\Pdf\EcheancierPdf;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|
use Symfony\Component\HttpKernel\KernelInterface;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\Routing\Attribute\Route;
|
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|
use Twig\Environment;
|
|
|
|
#[Route('/admin/echeancier', name: 'app_admin_echeancier_')]
|
|
#[IsGranted('ROLE_EMPLOYE')]
|
|
class EcheancierController extends AbstractController
|
|
{
|
|
private const MSG_NOT_FOUND = 'Echeancier introuvable';
|
|
|
|
public function __construct(
|
|
private EntityManagerInterface $em,
|
|
) {
|
|
}
|
|
|
|
#[Route('/create/{customerId}', name: 'create', requirements: ['customerId' => '\d+'], methods: ['POST'])]
|
|
public function create(int $customerId, Request $request, KernelInterface $kernel): Response
|
|
{
|
|
$customer = $this->em->getRepository(Customer::class)->find($customerId);
|
|
if (null === $customer) {
|
|
throw $this->createNotFoundException('Client introuvable');
|
|
}
|
|
|
|
// Bloquer si statut Danger
|
|
if ($this->isCustomerDanger($customer)) {
|
|
$this->addFlash('error', 'Creation bloquee : le client est en statut Danger (impayes ou echeancier annule avec rejets).');
|
|
|
|
return $this->redirectToRoute('app_admin_clients_show', ['id' => $customerId, 'tab' => 'echeancier']);
|
|
}
|
|
|
|
$description = trim($request->request->getString('description'));
|
|
$totalHt = $request->request->getString('totalHt');
|
|
$nbEcheances = $request->request->getInt('nbEcheances');
|
|
$startDate = $request->request->getString('startDate');
|
|
|
|
if ('' === $description || $nbEcheances < 2 || $nbEcheances > 36 || '' === $startDate) {
|
|
$this->addFlash('error', 'Donnees invalides. Minimum 2 echeances, maximum 36.');
|
|
|
|
return $this->redirectToRoute('app_admin_clients_show', ['id' => $customerId, 'tab' => 'echeancier']);
|
|
}
|
|
|
|
$totalHtFloat = (float) str_replace(',', '.', $totalHt);
|
|
$majoration = round($totalHtFloat * Echeancier::MAJORATION_RATE, 2);
|
|
$totalMajore = round($totalHtFloat + $majoration, 2);
|
|
$monthlyAmount = round($totalMajore / $nbEcheances, 2);
|
|
|
|
$echeancier = new Echeancier($customer, $description, number_format($totalHtFloat, 2, '.', ''));
|
|
|
|
// Lier a un avis de paiement si selectionne
|
|
$advertId = $request->request->getInt('advertId');
|
|
if ($advertId > 0) {
|
|
$advert = $this->em->getRepository(Advert::class)->find($advertId);
|
|
if (null !== $advert) {
|
|
$echeancier->setAdvert($advert);
|
|
}
|
|
}
|
|
|
|
/** @var \App\Entity\User|null $currentUser */
|
|
$currentUser = $this->getUser();
|
|
$echeancier->setSubmitterCompanyId($currentUser?->getId());
|
|
$echeancier->setSubmitterCustomerId($customer->getId());
|
|
|
|
$start = new \DateTimeImmutable($startDate);
|
|
|
|
for ($i = 1; $i <= $nbEcheances; ++$i) {
|
|
$scheduledAt = $start->modify('+'.($i - 1).' months');
|
|
$amount = $i === $nbEcheances
|
|
? number_format($totalMajore - ($monthlyAmount * ($nbEcheances - 1)), 2, '.', '')
|
|
: number_format($monthlyAmount, 2, '.', '');
|
|
|
|
$line = new EcheancierLine($echeancier, $i, $amount, $scheduledAt);
|
|
$echeancier->addLine($line);
|
|
$this->em->persist($line);
|
|
}
|
|
|
|
$this->em->persist($echeancier);
|
|
$this->em->flush();
|
|
|
|
// Generer le PDF automatiquement
|
|
$this->generateEcheancierPdf($echeancier, $kernel);
|
|
|
|
$this->addFlash('success', 'Echeancier cree avec '.$nbEcheances.' echeances de '.$monthlyAmount.' EUR/mois. PDF genere.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $echeancier->getId()]);
|
|
}
|
|
|
|
#[Route('/{id}', name: 'show', requirements: ['id' => '\d+'])]
|
|
public function show(int $id): Response
|
|
{
|
|
$echeancier = $this->em->getRepository(Echeancier::class)->find($id);
|
|
if (null === $echeancier) {
|
|
throw $this->createNotFoundException(self::MSG_NOT_FOUND);
|
|
}
|
|
|
|
return $this->render('admin/echeancier/show.html.twig', [
|
|
'echeancier' => $echeancier,
|
|
'customer' => $echeancier->getCustomer(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Envoie l'echeancier par email au client avec la proposition.
|
|
*/
|
|
#[Route('/{id}/send', name: 'send', requirements: ['id' => '\d+'], methods: ['POST'])]
|
|
public function send(int $id, MailerService $mailer, Environment $twig): Response
|
|
{
|
|
$echeancier = $this->em->getRepository(Echeancier::class)->find($id);
|
|
if (null === $echeancier) {
|
|
throw $this->createNotFoundException(self::MSG_NOT_FOUND);
|
|
}
|
|
|
|
$customer = $echeancier->getCustomer();
|
|
if (null === $customer->getEmail()) {
|
|
$this->addFlash('error', 'Email client introuvable.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
$html = $twig->render('emails/echeancier_proposition.html.twig', [
|
|
'customer' => $customer,
|
|
'echeancier' => $echeancier,
|
|
]);
|
|
|
|
$mailer->sendEmail(
|
|
$customer->getEmail(),
|
|
'Proposition d\'echeancier de paiement',
|
|
$html,
|
|
null,
|
|
null,
|
|
false,
|
|
);
|
|
|
|
$echeancier->setState(Echeancier::STATE_SEND);
|
|
$this->em->flush();
|
|
|
|
$this->addFlash('success', 'Proposition d\'echeancier envoyee a '.$customer->getEmail().'.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
/**
|
|
* Renvoie l'email de proposition au client.
|
|
*/
|
|
#[Route('/{id}/resend', name: 'resend', requirements: ['id' => '\d+'], methods: ['POST'])]
|
|
public function resend(int $id, MailerService $mailer, Environment $twig): Response
|
|
{
|
|
$echeancier = $this->em->getRepository(Echeancier::class)->find($id);
|
|
if (null === $echeancier) {
|
|
throw $this->createNotFoundException(self::MSG_NOT_FOUND);
|
|
}
|
|
|
|
$customer = $echeancier->getCustomer();
|
|
if (null === $customer->getEmail()) {
|
|
$this->addFlash('error', 'Email client introuvable.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
$html = $twig->render('emails/echeancier_proposition.html.twig', [
|
|
'customer' => $customer,
|
|
'echeancier' => $echeancier,
|
|
]);
|
|
|
|
$mailer->sendEmail(
|
|
$customer->getEmail(),
|
|
'Rappel - Proposition d\'echeancier de paiement',
|
|
$html,
|
|
null,
|
|
null,
|
|
false,
|
|
);
|
|
|
|
$this->addFlash('success', 'Rappel envoye a '.$customer->getEmail().'.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
/**
|
|
* Genere le PDF de l'echeancier.
|
|
*/
|
|
#[Route('/{id}/generate-pdf', name: 'generate_pdf', requirements: ['id' => '\d+'], methods: ['POST'])]
|
|
public function generatePdf(int $id, KernelInterface $kernel): Response
|
|
{
|
|
$echeancier = $this->em->getRepository(Echeancier::class)->find($id);
|
|
if (null === $echeancier) {
|
|
throw $this->createNotFoundException(self::MSG_NOT_FOUND);
|
|
}
|
|
|
|
$this->generateEcheancierPdf($echeancier, $kernel);
|
|
|
|
$this->addFlash('success', 'PDF echeancier '.($echeancier->getPdfUnsigned() ? 'regenere' : 'genere').'.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
private function isCustomerDanger(Customer $customer): bool
|
|
{
|
|
// Dernier avertissement = Danger
|
|
if ('last' === $customer->getWarningLevel()) {
|
|
return true;
|
|
}
|
|
|
|
// Compter avis impayes
|
|
$adverts = $this->em->getRepository(Advert::class)->findBy(['customer' => $customer]);
|
|
$nbUnpaidAdverts = 0;
|
|
foreach ($adverts as $advert) {
|
|
if (\in_array($advert->getState(), [Advert::STATE_CREATED, Advert::STATE_SEND], true)) {
|
|
++$nbUnpaidAdverts;
|
|
}
|
|
}
|
|
|
|
// Verifier echeanciers
|
|
$echeanciers = $this->em->getRepository(Echeancier::class)->findBy(['customer' => $customer]);
|
|
$hasCancelledWithRejects = false;
|
|
$hasUnpaidEcheancier = false;
|
|
foreach ($echeanciers as $echeancier) {
|
|
if (Echeancier::STATE_CANCELLED === $echeancier->getState() && $echeancier->getNbFailed() > 0) {
|
|
$hasCancelledWithRejects = true;
|
|
}
|
|
if (\in_array($echeancier->getState(), [Echeancier::STATE_ACTIVE, Echeancier::STATE_PENDING_SETUP, Echeancier::STATE_SIGNED], true)) {
|
|
$hasUnpaidEcheancier = true;
|
|
}
|
|
}
|
|
|
|
$totalUnpaid = $nbUnpaidAdverts + ($hasUnpaidEcheancier ? 1 : 0);
|
|
|
|
return $hasCancelledWithRejects || $nbUnpaidAdverts >= 3 || $totalUnpaid >= 2;
|
|
}
|
|
|
|
private function generateEcheancierPdf(Echeancier $echeancier, KernelInterface $kernel): void
|
|
{
|
|
$pdf = new EcheancierPdf($kernel, $echeancier);
|
|
$pdf->generate();
|
|
|
|
$tmpPath = tempnam(sys_get_temp_dir(), 'echeancier_').'.pdf';
|
|
$pdf->Output('F', $tmpPath);
|
|
|
|
$echeancier->setPdfUnsignedFile(new UploadedFile(
|
|
$tmpPath,
|
|
'echeancier-'.$echeancier->getId().'.pdf',
|
|
'application/pdf',
|
|
null,
|
|
true,
|
|
));
|
|
$echeancier->setUpdatedAt(new \DateTimeImmutable());
|
|
$this->em->flush();
|
|
|
|
@unlink($tmpPath);
|
|
}
|
|
|
|
/**
|
|
* Envoie le PDF pour signature via DocuSeal (2 parties : Company auto-signe + Client signe).
|
|
*/
|
|
#[Route('/{id}/send-signature', name: 'send_signature', requirements: ['id' => '\d+'], methods: ['POST'])]
|
|
public function sendSignature(
|
|
int $id,
|
|
DocuSealService $docuSeal,
|
|
MailerService $mailer,
|
|
Environment $twig,
|
|
UrlGeneratorInterface $urlGenerator,
|
|
#[Autowire(env: 'DOCUSEAL_URL')] string $docuSealUrl = '',
|
|
#[Autowire('%kernel.project_dir%')] string $projectDir = '',
|
|
): Response {
|
|
$echeancier = $this->em->getRepository(Echeancier::class)->find($id);
|
|
if (null === $echeancier) {
|
|
throw $this->createNotFoundException(self::MSG_NOT_FOUND);
|
|
}
|
|
|
|
$customer = $echeancier->getCustomer();
|
|
if (null === $echeancier->getPdfUnsigned() || null === $customer->getEmail()) {
|
|
$this->addFlash('error', null === $echeancier->getPdfUnsigned() ? 'Le PDF doit etre genere avant l\'envoi.' : 'Email client introuvable.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
$pdfPath = $projectDir.'/public/uploads/echeanciers/'.$echeancier->getPdfUnsigned();
|
|
if (!file_exists($pdfPath)) {
|
|
$this->addFlash('error', 'Fichier PDF introuvable.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
$signedRedirectUrl = $urlGenerator->generate('app_echeancier_signed', [
|
|
'id' => $echeancier->getId(),
|
|
], UrlGeneratorInterface::ABSOLUTE_URL);
|
|
|
|
try {
|
|
$pdfBase64 = base64_encode(file_get_contents($pdfPath));
|
|
|
|
$result = $docuSeal->getApi()->createSubmissionFromPdf([
|
|
'name' => 'Echeancier - '.$customer->getFullName(),
|
|
'send_email' => false,
|
|
'flatten' => true,
|
|
'documents' => [
|
|
[
|
|
'name' => 'echeancier-'.$echeancier->getId().'.pdf',
|
|
'file' => 'data:application/pdf;base64,'.$pdfBase64,
|
|
],
|
|
],
|
|
'submitters' => [
|
|
[
|
|
'email' => 'client@e-cosplay.fr',
|
|
'name' => 'Association E-Cosplay',
|
|
'role' => 'Company',
|
|
'completed' => true,
|
|
'send_email' => false,
|
|
'values' => ['Sign' => $docuSeal->getLogoBase64()],
|
|
'metadata' => ['doc_type' => 'echeancier', 'echeancier_id' => $echeancier->getId()],
|
|
],
|
|
[
|
|
'email' => $customer->getEmail(),
|
|
'name' => $customer->getFullName(),
|
|
'role' => 'First Party',
|
|
'send_email' => false,
|
|
'completed_redirect_url' => $signedRedirectUrl,
|
|
'metadata' => ['doc_type' => 'echeancier', 'echeancier_id' => $echeancier->getId()],
|
|
],
|
|
],
|
|
]);
|
|
|
|
$submitterId = $result['submitters'][1]['id'] ?? ($result[1]['id'] ?? null);
|
|
if (null !== $submitterId) {
|
|
$echeancier->setSubmissionId((string) $submitterId);
|
|
$echeancier->setState(Echeancier::STATE_SEND);
|
|
$this->em->flush();
|
|
|
|
// Envoyer email au client avec lien de signature
|
|
$slug = $docuSeal->getSubmitterSlug($submitterId);
|
|
$signUrl = null !== $slug ? rtrim($docuSealUrl, '/').'/s/'.$slug : null;
|
|
|
|
$processUrl = $urlGenerator->generate('app_echeancier_verify', [
|
|
'id' => $echeancier->getId(),
|
|
], UrlGeneratorInterface::ABSOLUTE_URL);
|
|
|
|
$html = $twig->render('emails/echeancier_signature.html.twig', [
|
|
'customer' => $customer,
|
|
'echeancier' => $echeancier,
|
|
'signUrl' => $signUrl,
|
|
'processUrl' => $processUrl,
|
|
]);
|
|
|
|
$mailer->sendEmail(
|
|
$customer->getEmail(),
|
|
'Echeancier a signer - '.$customer->getFullName(),
|
|
$html,
|
|
null,
|
|
null,
|
|
false,
|
|
);
|
|
|
|
$this->addFlash('success', 'Echeancier envoye pour signature a '.$customer->getEmail().'.');
|
|
} else {
|
|
$this->addFlash('error', 'Erreur DocuSeal : aucun submitter retourne.');
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$this->addFlash('error', 'Erreur DocuSeal : '.$e->getMessage());
|
|
}
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
/**
|
|
* Envoie une attestation d'etat de l'echeancier au client.
|
|
*/
|
|
#[Route('/{id}/send-attestation', name: 'send_attestation', requirements: ['id' => '\d+'], methods: ['POST'])]
|
|
public function sendAttestation(
|
|
int $id,
|
|
MailerService $mailer,
|
|
Environment $twig,
|
|
KernelInterface $kernel,
|
|
DocuSealService $docuSeal,
|
|
): Response {
|
|
$echeancier = $this->em->getRepository(Echeancier::class)->find($id);
|
|
if (null === $echeancier) {
|
|
throw $this->createNotFoundException(self::MSG_NOT_FOUND);
|
|
}
|
|
|
|
$customer = $echeancier->getCustomer();
|
|
if (null === $customer->getEmail()) {
|
|
$this->addFlash('error', 'Email client introuvable.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
// Generer le PDF attestation
|
|
$pdf = new \App\Service\Pdf\EcheancierAttestationPdf($kernel, $echeancier);
|
|
$pdf->generate();
|
|
|
|
$tmpPath = tempnam(sys_get_temp_dir(), 'ech_att_').'.pdf';
|
|
$pdf->Output('F', $tmpPath);
|
|
|
|
// Envoyer a DocuSeal pour auto-signature
|
|
// Le mail sera envoye au retour du webhook form.completed (doc_type=echeancier_attestation)
|
|
// @codeCoverageIgnoreStart
|
|
try {
|
|
$pdfBase64 = base64_encode(file_get_contents($tmpPath));
|
|
|
|
$docuSeal->getApi()->createSubmissionFromPdf([
|
|
'name' => 'Attestation '.$echeancier->getReference(),
|
|
'send_email' => false,
|
|
'flatten' => true,
|
|
'documents' => [[
|
|
'name' => 'attestation-'.$echeancier->getReference().'.pdf',
|
|
'file' => 'data:application/pdf;base64,'.$pdfBase64,
|
|
]],
|
|
'submitters' => [[
|
|
'email' => 'client@e-cosplay.fr',
|
|
'name' => 'Association E-Cosplay',
|
|
'role' => 'First Party',
|
|
'completed' => true,
|
|
'send_email' => false,
|
|
'values' => ['Sign' => $docuSeal->getLogoBase64()],
|
|
'metadata' => [
|
|
'doc_type' => 'echeancier_attestation',
|
|
'echeancier_id' => $echeancier->getId(),
|
|
],
|
|
]],
|
|
]);
|
|
|
|
$this->addFlash('success', 'Attestation envoyee pour signature. Le client recevra le PDF signe automatiquement.');
|
|
} catch (\Throwable $e) {
|
|
$this->addFlash('error', 'Erreur DocuSeal : '.$e->getMessage());
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
@unlink($tmpPath);
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
/**
|
|
* Reinitialise le moyen de paiement SEPA et renvoie le lien de configuration au client.
|
|
*/
|
|
#[Route('/{id}/reset-sepa', name: 'reset_sepa', requirements: ['id' => '\d+'], methods: ['POST'])]
|
|
public function resetSepa(
|
|
int $id,
|
|
MailerService $mailer,
|
|
Environment $twig,
|
|
UrlGeneratorInterface $urlGenerator,
|
|
): Response {
|
|
$echeancier = $this->em->getRepository(Echeancier::class)->find($id);
|
|
if (null === $echeancier) {
|
|
throw $this->createNotFoundException(self::MSG_NOT_FOUND);
|
|
}
|
|
|
|
$echeancier->setStripePaymentMethodId(null);
|
|
$echeancier->setState(Echeancier::STATE_PENDING_SETUP);
|
|
$this->em->flush();
|
|
|
|
$customer = $echeancier->getCustomer();
|
|
if (null !== $customer->getEmail()) {
|
|
$setupUrl = $urlGenerator->generate('app_echeancier_setup_payment', [
|
|
'id' => $echeancier->getId(),
|
|
], UrlGeneratorInterface::ABSOLUTE_URL);
|
|
|
|
$mailer->sendEmail(
|
|
$customer->getEmail(),
|
|
'Configurez votre prelevement SEPA - Echeancier '.$echeancier->getReference(),
|
|
$twig->render('emails/echeancier_stripe_setup.html.twig', [
|
|
'customer' => $customer,
|
|
'echeancier' => $echeancier,
|
|
'setupUrl' => $setupUrl,
|
|
]),
|
|
null,
|
|
null,
|
|
false,
|
|
);
|
|
|
|
$this->addFlash('success', 'Moyen de paiement reinitialise. Nouveau lien SEPA envoye a '.$customer->getEmail().'.');
|
|
} else {
|
|
$this->addFlash('success', 'Moyen de paiement reinitialise.');
|
|
}
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
/**
|
|
* Force le prelevement d'une echeance via PaymentIntent.
|
|
*/
|
|
#[Route('/{id}/force-payment/{lineId}', name: 'force_payment', requirements: ['id' => '\d+', 'lineId' => '\d+'], methods: ['POST'])]
|
|
public function forcePayment(
|
|
int $id,
|
|
int $lineId,
|
|
#[Autowire(env: 'STRIPE_SK')] string $stripeSk = '',
|
|
): Response {
|
|
$echeancier = $this->em->getRepository(Echeancier::class)->find($id);
|
|
if (null === $echeancier) {
|
|
throw $this->createNotFoundException(self::MSG_NOT_FOUND);
|
|
}
|
|
|
|
$line = $this->em->getRepository(EcheancierLine::class)->find($lineId);
|
|
if (null === $line || $line->getEcheancier()->getId() !== $echeancier->getId()) {
|
|
throw $this->createNotFoundException('Echeance introuvable');
|
|
}
|
|
|
|
if (null === $echeancier->getStripePaymentMethodId() || null === $echeancier->getStripeCustomerId()) {
|
|
$this->addFlash('error', 'SEPA non configure pour cet echeancier.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
if ('' === $stripeSk) {
|
|
$this->addFlash('error', 'Stripe non configure.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
// @codeCoverageIgnoreStart
|
|
try {
|
|
\Stripe\Stripe::setApiKey($stripeSk);
|
|
|
|
$pi = \Stripe\PaymentIntent::create([
|
|
'amount' => (int) round((float) $line->getAmount() * 100),
|
|
'currency' => 'eur',
|
|
'customer' => $echeancier->getStripeCustomerId(),
|
|
'payment_method' => $echeancier->getStripePaymentMethodId(),
|
|
'off_session' => true,
|
|
'confirm' => true,
|
|
'payment_method_types' => ['sepa_debit'],
|
|
'metadata' => [
|
|
'echeancier_id' => (string) $echeancier->getId(),
|
|
'echeancier_line_id' => (string) $line->getId(),
|
|
'position' => (string) $line->getPosition(),
|
|
'reference' => $echeancier->getReference(),
|
|
],
|
|
'description' => $line->getLabel().' - '.$echeancier->getReference(),
|
|
]);
|
|
|
|
// Remettre en prepared si echoue precedemment
|
|
if (EcheancierLine::STATE_KO === $line->getState()) {
|
|
$line->setState(EcheancierLine::STATE_PREPARED);
|
|
$line->setFailureReason(null);
|
|
}
|
|
|
|
$line->setStripePaymentIntentId($pi->id);
|
|
$this->em->flush();
|
|
|
|
$this->addFlash('success', 'Prelevement lance pour l\'echeance '.$line->getPosition().' ('.$line->getAmount().' EUR). Le resultat sera recu via webhook.');
|
|
} catch (\Throwable $e) {
|
|
$this->addFlash('error', 'Erreur Stripe : '.$e->getMessage());
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
/**
|
|
* Annule un echeancier.
|
|
*/
|
|
#[Route('/{id}/cancel', name: 'cancel', requirements: ['id' => '\d+'], methods: ['POST'])]
|
|
public function cancel(int $id): Response
|
|
{
|
|
$echeancier = $this->em->getRepository(Echeancier::class)->find($id);
|
|
if (null === $echeancier) {
|
|
throw $this->createNotFoundException(self::MSG_NOT_FOUND);
|
|
}
|
|
|
|
$echeancier->setState(Echeancier::STATE_CANCELLED);
|
|
$this->em->flush();
|
|
|
|
$this->addFlash('success', 'Echeancier annule.');
|
|
|
|
return $this->redirectToRoute('app_admin_clients_show', [
|
|
'id' => $echeancier->getCustomer()->getId(),
|
|
'tab' => 'echeancier',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Envoie le lien de configuration SEPA au client.
|
|
*/
|
|
#[Route('/{id}/send-sepa', name: 'send_sepa', requirements: ['id' => '\d+'], methods: ['POST'])]
|
|
public function sendSepa(
|
|
int $id,
|
|
MailerService $mailer,
|
|
Environment $twig,
|
|
UrlGeneratorInterface $urlGenerator,
|
|
#[Autowire(env: 'STRIPE_SK')] string $stripeSk = '',
|
|
): Response {
|
|
$echeancier = $this->em->getRepository(Echeancier::class)->find($id);
|
|
if (null === $echeancier) {
|
|
throw $this->createNotFoundException(self::MSG_NOT_FOUND);
|
|
}
|
|
|
|
if (!\in_array($echeancier->getState(), [Echeancier::STATE_SIGNED, Echeancier::STATE_PENDING_SETUP], true)) {
|
|
$this->addFlash('error', 'L\'echeancier doit etre signe pour envoyer le lien SEPA.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
$customer = $echeancier->getCustomer();
|
|
if (null === $customer->getEmail()) {
|
|
$this->addFlash('error', 'Email client introuvable.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
if ('' === $stripeSk) {
|
|
$this->addFlash('error', 'Stripe non configure.');
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
|
|
// @codeCoverageIgnoreStart
|
|
try {
|
|
\Stripe\Stripe::setApiKey($stripeSk);
|
|
|
|
$stripeCustomerId = $customer->getStripeCustomerId();
|
|
if (null === $stripeCustomerId) {
|
|
$stripeCustomer = \Stripe\Customer::create([
|
|
'email' => $customer->getEmail(),
|
|
'name' => $customer->getFullName(),
|
|
]);
|
|
$stripeCustomerId = $stripeCustomer->id;
|
|
$customer->setStripeCustomerId($stripeCustomerId);
|
|
}
|
|
|
|
$echeancier->setStripeCustomerId($stripeCustomerId);
|
|
$echeancier->setState(Echeancier::STATE_PENDING_SETUP);
|
|
$this->em->flush();
|
|
|
|
$setupUrl = $urlGenerator->generate('app_echeancier_setup_payment', [
|
|
'id' => $echeancier->getId(),
|
|
], UrlGeneratorInterface::ABSOLUTE_URL);
|
|
|
|
$mailer->sendEmail(
|
|
$customer->getEmail(),
|
|
'Configurez votre prelevement SEPA - Echeancier '.$echeancier->getReference(),
|
|
$twig->render('emails/echeancier_stripe_setup.html.twig', [
|
|
'customer' => $customer,
|
|
'echeancier' => $echeancier,
|
|
'setupUrl' => $setupUrl,
|
|
]),
|
|
null,
|
|
null,
|
|
false,
|
|
);
|
|
|
|
$this->addFlash('success', 'Lien de configuration SEPA envoye a '.$customer->getEmail().'.');
|
|
} catch (\Throwable $e) {
|
|
$this->addFlash('error', 'Erreur Stripe : '.$e->getMessage());
|
|
}
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
return $this->redirectToRoute('app_admin_echeancier_show', ['id' => $id]);
|
|
}
|
|
}
|