add webhooks for refused signature devis

This commit is contained in:
Serreau Jovann
2026-02-16 08:57:53 +01:00
parent 1caffbfbb7
commit f074638719
5 changed files with 137 additions and 9 deletions

6
.env
View File

@@ -83,9 +83,9 @@ STRIPE_PK=pk_test_51SUA22173W4aeFB1nO6oFfDZ12HOTffDKtCshhZ8rkUg6kUO2ZaQC0tK72rhE
STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR
STRIPE_WEBHOOKS_SECRET=
SIGN_URL=https://eb44-82-67-166-187.ngrok-free.app
STRIPE_BASEURL=https://eb44-82-67-166-187.ngrok-free.app
CONTRAT_BASEURL=https://eb44-82-67-166-187.ngrok-free.app
SIGN_URL=https://00ca-212-114-31-239.ngrok-free.app
STRIPE_BASEURL=https://00ca-212-114-31-239.ngrok-free.app
CONTRAT_BASEURL=https://00ca-212-114-31-239.ngrok-free.app
MINIO_S3_URL=
MINIO_S3_CLIENT_ID=

View File

@@ -51,9 +51,15 @@ class HomeController extends AbstractController
$stats = $this->getUmamiStats();
$updates = $this->getSystemUpdates();
$z = [];
foreach ($this->devisRepository->waitSign() as $item) {
if(!str_contains($item['state'],'refused')) {
$z[] = $item;
}
}
return $this->render('dashboard/home.twig', [
'product' => $this->productRepository->count([]),
'devis_wait_sign' => $this->devisRepository->waitSign(),
'devis_wait_sign' => count($z),
'customers' => $this->customerRepository->count([]),
'nbVisitor' => $stats['visitors'],
'nbView' => $stats['views'],

View File

@@ -2,11 +2,15 @@
namespace App\Controller;
use App\Entity\Contrats;
use App\Entity\ContratsPayments;
use App\Entity\Devis;
use App\Entity\Product;
use App\Entity\ProductReserve;
use App\Kernel;
use App\Logger\AppLogger;
use App\Repository\ContratsRepository;
use App\Repository\DevisRepository;
use App\Repository\ProductRepository;
use App\Service\Mailer\Mailer;
use App\Service\Pdf\PlPdf;
@@ -71,7 +75,7 @@ class Webhooks extends AbstractController
$pl->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $pl->getId() . ".pdf", "application/pdf", null, true));
$pl->setUpdateAt(new \DateTimeImmutable('now'));
$entityManager->persist($pl);
// Update Reservation State if Accompte and Signed
if ($pl->getType() === 'accompte' && $contrat->isSigned()) {
$contrat->setReservationState('ready');
@@ -82,7 +86,7 @@ class Webhooks extends AbstractController
if ($prestataire) {
$dateStart = $contrat->getDateAt()->format('d/m/Y');
$dateEnd = $contrat->getEndAt()->format('d/m/Y');
$mailer->send(
$prestataire->getEmail(),
$prestataire->getSurname() . ' ' . $prestataire->getName(),
@@ -97,7 +101,7 @@ class Webhooks extends AbstractController
);
}
}
$entityManager->flush();
// --- ENVOI DES EMAILS ---
@@ -159,4 +163,77 @@ class Webhooks extends AbstractController
return new Response('Invalid Signature', 400);
}
#[Route(path: '/webhooks/docuseal', name: 'webhooks_docuseal', options: ['sitemap' => false], methods: ['POST'])]
public function docuseal(
Mailer $mailer,
EntityManagerInterface $entityManager,
ContratsRepository $contratsRepository,
DevisRepository $devisRepository,
Client $client,
Request $request,
AppLogger $appLogger
): Response {
$contentRaw = $request->getContent();
$headers = $request->headers;
// Vérification de sécurité (Header personnalisé)
if ($headers->has('X-Src') && $headers->get('X-Src') === "Ludik_Sign") {
$payload = json_decode($contentRaw);
// Validation de la structure du payload
if (!isset($payload->data) || !isset($payload->data->metadata)) {
return new Response("Invalid payload structure", 400);
}
$content = $payload->data;
$metadata = $content->metadata;
$status = $content->status ?? '';
// --- TRAITEMENT DES DEVIS ---
if (isset($metadata->type) && $metadata->type === "devis" && isset($metadata->id)) {
$devis = $devisRepository->find($metadata->id);
if ($devis instanceof Devis) {
// Cas 1 : Signature refusée par le client
if ($status === "declined") {
$reason = $content->decline_reason ?? 'Aucune raison spécifiée';
// Mise à jour du statut
$devis->setState("refused|" . $reason);
$entityManager->persist($devis);
$entityManager->flush();
// Notification au client (Confirmation de prise en compte du refus)
$customer = $devis->getCustomer();
if ($customer) {
$mailer->send(
$customer->getEmail(),
$customer->getName() . " " . $customer->getSurname(),
"[Intranet Ludikevent] - Le client a refusé la signature du devis n°" . $devis->getNum(),
"mails/customer/devis_refuses.twig",
[
'devis' => $devis,
'raison' => $reason,
]
);
}
$appLogger->record('WEBHOOK', sprintf("Devis %s refusé par le client. Raison: %s", $devis->getNum(), $reason));
return new Response("ok-devis-declined");
}
// Cas 2 : Signature complétée (Optionnel - à implémenter selon vos besoins)
if ($status === "completed") {
// Logique de succès (ex: passage en contrat)
$appLogger->record('WEBHOOK', sprintf("Devis %s signé avec succès.", $devis->getNum()));
return new Response("ok-devis-completed");
}
}
}
}
return new Response("ignored");
}
}

View File

@@ -42,12 +42,12 @@ class DevisRepository extends ServiceEntityRepository
// }
public function waitSign()
{
return count( $this->createQueryBuilder('d')
return $this->createQueryBuilder('d')
->andWhere('d.state != :status')
->andWhere('d.state != :status2')
->setParameter('status','signed')
->setParameter('status2','cancel')
->getQuery()->getArrayResult());
->getQuery()->getArrayResult();
}
}

View File

@@ -0,0 +1,45 @@
{% extends 'mails/base.twig' %}
{% block content %}
<mj-section background-color="#ffffff" padding-top="20px">
<mj-column width="100%">
<mj-text font-size="14px" color="#475569" font-family="Arial, sans-serif" line-height="1.6">
Le devis n°<strong>{{ datas.devis.num }}</strong> a été <span style="color: #ef4444; font-weight: bold;">refusé</span> par le client <strong>{{ datas.devis.customer.name }} {{ datas.devis.customer.surname }}</strong>.
</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="0px">
<mj-column width="90%" background-color="#fef2f2" border="1px solid #fecaca" border-radius="8px">
<mj-table font-family="Arial, sans-serif" font-size="14px" padding="15px">
<tr>
<td style="padding: 5px 0; color: #64748b;">N° Devis :</td>
<td style="padding: 5px 0; text-align: right; font-weight: bold;">{{ datas.devis.num }}</td>
</tr>
<tr>
<td style="padding: 5px 0; color: #64748b;">Client :</td>
<td style="padding: 5px 0; text-align: right;">{{ datas.devis.customer.name }} {{ datas.devis.customer.surname }}</td>
</tr>
<tr>
<td style="padding: 5px 0; color: #64748b;">Raison du refus :</td>
<td style="padding: 5px 0; text-align: right; font-weight: bold; color: #ef4444;">{{ datas.raison }}</td>
</tr>
<tr>
<td style="padding: 5px 0; color: #64748b;">Date :</td>
<td style="padding: 5px 0; text-align: right;">{{ "now"|date("d/m/Y à H:i") }}</td>
</tr>
</mj-table>
</mj-column>
</mj-section>
<mj-section background-color="#ffffff">
<mj-column width="100%">
<mj-text font-size="13px" color="#64748b" font-family="Arial, sans-serif" font-style="italic" align="center">
Ce devis est désormais marqué comme "Refusé" dans le CRM. Une relance commerciale est peut-être nécessaire.
</mj-text>
<mj-button background-color="#1e293b" color="white" font-size="14px" font-weight="bold" href="https://intranet.ludikevent.fr/crm/devis" border-radius="6px" padding-top="20px">
GÉRER LE DEVIS
</mj-button>
</mj-column>
</mj-section>
{% endblock %}