add webhooks for refused signature devis
This commit is contained in:
6
.env
6
.env
@@ -83,9 +83,9 @@ STRIPE_PK=pk_test_51SUA22173W4aeFB1nO6oFfDZ12HOTffDKtCshhZ8rkUg6kUO2ZaQC0tK72rhE
|
|||||||
STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR
|
STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR
|
||||||
STRIPE_WEBHOOKS_SECRET=
|
STRIPE_WEBHOOKS_SECRET=
|
||||||
|
|
||||||
SIGN_URL=https://eb44-82-67-166-187.ngrok-free.app
|
SIGN_URL=https://00ca-212-114-31-239.ngrok-free.app
|
||||||
STRIPE_BASEURL=https://eb44-82-67-166-187.ngrok-free.app
|
STRIPE_BASEURL=https://00ca-212-114-31-239.ngrok-free.app
|
||||||
CONTRAT_BASEURL=https://eb44-82-67-166-187.ngrok-free.app
|
CONTRAT_BASEURL=https://00ca-212-114-31-239.ngrok-free.app
|
||||||
|
|
||||||
MINIO_S3_URL=
|
MINIO_S3_URL=
|
||||||
MINIO_S3_CLIENT_ID=
|
MINIO_S3_CLIENT_ID=
|
||||||
|
|||||||
@@ -51,9 +51,15 @@ class HomeController extends AbstractController
|
|||||||
$stats = $this->getUmamiStats();
|
$stats = $this->getUmamiStats();
|
||||||
$updates = $this->getSystemUpdates();
|
$updates = $this->getSystemUpdates();
|
||||||
|
|
||||||
|
$z = [];
|
||||||
|
foreach ($this->devisRepository->waitSign() as $item) {
|
||||||
|
if(!str_contains($item['state'],'refused')) {
|
||||||
|
$z[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
return $this->render('dashboard/home.twig', [
|
return $this->render('dashboard/home.twig', [
|
||||||
'product' => $this->productRepository->count([]),
|
'product' => $this->productRepository->count([]),
|
||||||
'devis_wait_sign' => $this->devisRepository->waitSign(),
|
'devis_wait_sign' => count($z),
|
||||||
'customers' => $this->customerRepository->count([]),
|
'customers' => $this->customerRepository->count([]),
|
||||||
'nbVisitor' => $stats['visitors'],
|
'nbVisitor' => $stats['visitors'],
|
||||||
'nbView' => $stats['views'],
|
'nbView' => $stats['views'],
|
||||||
|
|||||||
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Contrats;
|
||||||
use App\Entity\ContratsPayments;
|
use App\Entity\ContratsPayments;
|
||||||
use App\Entity\Devis;
|
use App\Entity\Devis;
|
||||||
use App\Entity\Product;
|
use App\Entity\Product;
|
||||||
use App\Entity\ProductReserve;
|
use App\Entity\ProductReserve;
|
||||||
use App\Kernel;
|
use App\Kernel;
|
||||||
|
use App\Logger\AppLogger;
|
||||||
|
use App\Repository\ContratsRepository;
|
||||||
|
use App\Repository\DevisRepository;
|
||||||
use App\Repository\ProductRepository;
|
use App\Repository\ProductRepository;
|
||||||
use App\Service\Mailer\Mailer;
|
use App\Service\Mailer\Mailer;
|
||||||
use App\Service\Pdf\PlPdf;
|
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->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $pl->getId() . ".pdf", "application/pdf", null, true));
|
||||||
$pl->setUpdateAt(new \DateTimeImmutable('now'));
|
$pl->setUpdateAt(new \DateTimeImmutable('now'));
|
||||||
$entityManager->persist($pl);
|
$entityManager->persist($pl);
|
||||||
|
|
||||||
// Update Reservation State if Accompte and Signed
|
// Update Reservation State if Accompte and Signed
|
||||||
if ($pl->getType() === 'accompte' && $contrat->isSigned()) {
|
if ($pl->getType() === 'accompte' && $contrat->isSigned()) {
|
||||||
$contrat->setReservationState('ready');
|
$contrat->setReservationState('ready');
|
||||||
@@ -82,7 +86,7 @@ class Webhooks extends AbstractController
|
|||||||
if ($prestataire) {
|
if ($prestataire) {
|
||||||
$dateStart = $contrat->getDateAt()->format('d/m/Y');
|
$dateStart = $contrat->getDateAt()->format('d/m/Y');
|
||||||
$dateEnd = $contrat->getEndAt()->format('d/m/Y');
|
$dateEnd = $contrat->getEndAt()->format('d/m/Y');
|
||||||
|
|
||||||
$mailer->send(
|
$mailer->send(
|
||||||
$prestataire->getEmail(),
|
$prestataire->getEmail(),
|
||||||
$prestataire->getSurname() . ' ' . $prestataire->getName(),
|
$prestataire->getSurname() . ' ' . $prestataire->getName(),
|
||||||
@@ -97,7 +101,7 @@ class Webhooks extends AbstractController
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
|
|
||||||
// --- ENVOI DES EMAILS ---
|
// --- ENVOI DES EMAILS ---
|
||||||
@@ -159,4 +163,77 @@ class Webhooks extends AbstractController
|
|||||||
|
|
||||||
return new Response('Invalid Signature', 400);
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ class DevisRepository extends ServiceEntityRepository
|
|||||||
// }
|
// }
|
||||||
public function waitSign()
|
public function waitSign()
|
||||||
{
|
{
|
||||||
return count( $this->createQueryBuilder('d')
|
return $this->createQueryBuilder('d')
|
||||||
->andWhere('d.state != :status')
|
->andWhere('d.state != :status')
|
||||||
->andWhere('d.state != :status2')
|
->andWhere('d.state != :status2')
|
||||||
->setParameter('status','signed')
|
->setParameter('status','signed')
|
||||||
->setParameter('status2','cancel')
|
->setParameter('status2','cancel')
|
||||||
->getQuery()->getArrayResult());
|
->getQuery()->getArrayResult();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
templates/mails/customer/devis_refuses.twig
Normal file
45
templates/mails/customer/devis_refuses.twig
Normal 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 %}
|
||||||
Reference in New Issue
Block a user