feat(contrat/paiement): Ajoute la confirmation de paiement avec signature automatique.
```
This commit is contained in:
Serreau Jovann
2026-01-23 10:04:12 +01:00
parent 79e964d7d0
commit c351c239c5
9 changed files with 491 additions and 4 deletions

View File

@@ -45,3 +45,11 @@ vich_uploader:
uri_prefix: /pdf/devis_audit uri_prefix: /pdf/devis_audit
upload_destination: '%kernel.project_dir%/public/pdf/devis_audit' upload_destination: '%kernel.project_dir%/public/pdf/devis_audit'
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
payment_confirmed:
uri_prefix: /pdf/payment_confirmed
upload_destination: '%kernel.project_dir%/public/pdf/payment_confirmed'
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
payment_confirmed_signed:
uri_prefix: /pdf/payment_confirmed_signed
upload_destination: '%kernel.project_dir%/public/pdf/payment_confirmed_signed'
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer

View File

@@ -0,0 +1,41 @@
<?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 Version20260123085450 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 contrats_payments ADD payment_file_name VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE contrats_payments ADD payment_file_size INT DEFAULT NULL');
$this->addSql('ALTER TABLE contrats_payments ADD payment_signed_file_name VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE contrats_payments ADD payment_signed_file_size INT DEFAULT NULL');
$this->addSql('ALTER TABLE contrats_payments ADD update_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN contrats_payments.update_at IS \'(DC2Type:datetime_immutable)\'');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE contrats_payments DROP payment_file_name');
$this->addSql('ALTER TABLE contrats_payments DROP payment_file_size');
$this->addSql('ALTER TABLE contrats_payments DROP payment_signed_file_name');
$this->addSql('ALTER TABLE contrats_payments DROP payment_signed_file_size');
$this->addSql('ALTER TABLE contrats_payments DROP update_at');
}
}

View File

@@ -15,6 +15,7 @@ use App\Repository\ContratsRepository;
use App\Repository\CustomerAddressRepository; use App\Repository\CustomerAddressRepository;
use App\Repository\CustomerRepository; use App\Repository\CustomerRepository;
use App\Service\Mailer\Mailer; use App\Service\Mailer\Mailer;
use App\Service\Pdf\PlPdf;
use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent; use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent;
use App\Service\ResetPassword\Event\ResetPasswordEvent; use App\Service\ResetPassword\Event\ResetPasswordEvent;
use App\Service\Signature\Client; use App\Service\Signature\Client;
@@ -24,14 +25,18 @@ use Symfony\Bundle\SecurityBundle\Security;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry; use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
class ContratController extends AbstractController class ContratController extends AbstractController
@@ -126,7 +131,7 @@ class ContratController extends AbstractController
} }
#[Route('/reservation/gestion-contrat/{num}', name: 'gestion_contrat_view')] #[Route('/reservation/gestion-contrat/{num}', name: 'gestion_contrat_view')]
public function gestionContratView(string $num,\App\Service\Stripe\Client $stripeClient,Client $client,Mailer $mailer,EntityManagerInterface $entityManager, Request $request, ContratsRepository $contratsRepository): Response public function gestionContratView(string $num,UploaderHelper $uploaderHelper,KernelInterface $kernel,\App\Service\Stripe\Client $stripeClient,Client $client,Mailer $mailer,EntityManagerInterface $entityManager, Request $request, ContratsRepository $contratsRepository): Response
{ {
$contrat = $contratsRepository->findOneBy(['numReservation' => $num]); $contrat = $contratsRepository->findOneBy(['numReservation' => $num]);
@@ -296,6 +301,37 @@ class ContratController extends AbstractController
return new RedirectResponse($result['url']); return new RedirectResponse($result['url']);
} }
if($request->query->get('idPaymentPdf')) {
$pl = $entityManager->getRepository(ContratsPayments::class)->find($request->query->get('idPaymentPdf'));
if($pl instanceof ContratsPayments && $pl->getState() == "complete") {
if ($pl->getPaymentSignedFileName() == null) {
$pdf = new PlPdf($kernel, $pl, $contrat);
$pdf->generate();
$content = $pdf->Output('S');
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
file_put_contents($tmpSigned, $content);
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
$pl->setPaymentFile(new UploadedFile($tmpSigned, "confirmed-" . $pl->getId() . ".pdf", "application/pdf", null, true));
$pl->setUpdateAt(new \DateTimeImmutable('now'));
$entityManager->persist($pl);
$entityManager->flush();
$data = $client->autoSignConfirmedPayment($pl);
// 1. Gestion du PDF SIGNÉ
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
$signedContent = file_get_contents($data);
file_put_contents($tmpSigned, $signedContent);
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
$pl->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $pl->getId() . ".pdf", "application/pdf", null, true));
$pl->setUpdateAt(new \DateTimeImmutable('now'));
$entityManager->persist($pl);
$entityManager->flush();
}
$path= $kernel->getProjectDir() . "/public".$uploaderHelper->asset($pl,'paymentSignedFile');
return $this->file($path, 'confirmation-paiement.pdf', ResponseHeaderBag::DISPOSITION_INLINE);
}
}
return $this->render('reservation/contrat/view.twig', [ return $this->render('reservation/contrat/view.twig', [
'contrat' => $contrat, 'contrat' => $contrat,

View File

@@ -6,19 +6,23 @@ 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\Repository\ProductRepository; use App\Repository\ProductRepository;
use App\Service\Mailer\Mailer; use App\Service\Mailer\Mailer;
use App\Service\Pdf\PlPdf;
use App\Service\Stripe\Client; use App\Service\Stripe\Client;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
class Webhooks extends AbstractController class Webhooks extends AbstractController
{ {
#[Route(path: '/webhooks/payment-intent', name: 'webhooks_payment', options: ['sitemap' => false], methods: ['POST'])] #[Route(path: '/webhooks/payment-intent', name: 'webhooks_payment', options: ['sitemap' => false], methods: ['POST'])]
public function payment(Mailer $mailer, ProductRepository $productRepository, Request $request, Client $client, EntityManagerInterface $entityManager): Response public function payment(Mailer $mailer,KernelInterface $kernel,\App\Service\Signature\Client $sig, ProductRepository $productRepository, Request $request, Client $client, EntityManagerInterface $entityManager): Response
{ {
// 1. Vérification de la signature // 1. Vérification de la signature
if ($client->checkWebhooks($request, 'payment')) { if ($client->checkWebhooks($request, 'payment')) {
@@ -34,7 +38,6 @@ class Webhooks extends AbstractController
if ($paymentId) { if ($paymentId) {
$pl = $entityManager->getRepository(ContratsPayments::class)->findOneBy(['paymentId' => $paymentId]); $pl = $entityManager->getRepository(ContratsPayments::class)->findOneBy(['paymentId' => $paymentId]);
if ($pl instanceof ContratsPayments && $pl->getState() !== "complete") { if ($pl instanceof ContratsPayments && $pl->getState() !== "complete") {
// MAJ du paiement // MAJ du paiement
@@ -65,6 +68,27 @@ class Webhooks extends AbstractController
} }
} }
$pdf = new PlPdf($kernel, $pl, $contrat);
$pdf->generate();
$content = $pdf->Output('S');
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
file_put_contents($tmpSigned, $content);
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
$pl->setPaymentFile(new UploadedFile($tmpSigned, "confirmed-" . $pl->getId() . ".pdf", "application/pdf", null, true));
$pl->setUpdateAt(new \DateTimeImmutable('now'));
$entityManager->persist($pl);
$entityManager->flush();
$data = $sig->autoSignConfirmedPayment($pl);
// 1. Gestion du PDF SIGNÉ
$tmpSigned = sys_get_temp_dir() . '/sign_' . uniqid() . '.pdf';
$signedContent = file_get_contents($data);
file_put_contents($tmpSigned, $signedContent);
// On utilise UploadedFile pour simuler un upload propre pour VichUploader
$pl->setPaymentSignedFile(new UploadedFile($tmpSigned, "confirmed-certificate-" . $pl->getId() . ".pdf", "application/pdf", null, true));
$pl->setUpdateAt(new \DateTimeImmutable('now'));
$entityManager->persist($pl);
$entityManager->flush(); $entityManager->flush();
// --- ENVOI DES EMAILS --- // --- ENVOI DES EMAILS ---

View File

@@ -5,8 +5,12 @@ namespace App\Entity;
use App\Repository\ContratsPaymentsRepository; use App\Repository\ContratsPaymentsRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Attribute\Uploadable;
use Vich\UploaderBundle\Mapping\Attribute\UploadableField;
#[ORM\Entity(repositoryClass: ContratsPaymentsRepository::class)] #[ORM\Entity(repositoryClass: ContratsPaymentsRepository::class)]
#[Uploadable]
class ContratsPayments class ContratsPayments
{ {
#[ORM\Id] #[ORM\Id]
@@ -38,6 +42,24 @@ class ContratsPayments
#[ORM\Column(type: Types::ARRAY,nullable: true)] #[ORM\Column(type: Types::ARRAY,nullable: true)]
private array $card = []; private array $card = [];
#[UploadableField(mapping: 'payment_confirmed', fileNameProperty: 'paymentFileName', size: 'paymentFileSize')]
private ?File $paymentFile = null;
#[ORM\Column(nullable: true)]
private ?string $paymentFileName = null;
#[ORM\Column(nullable: true)]
private ?int $paymentFileSize = null;
#[UploadableField(mapping: 'payment_confirmed_signed', fileNameProperty: 'paymentSignedFileName', size: 'paymentSignedFileSize')]
private ?File $paymentSignedFile = null;
#[ORM\Column(nullable: true)]
private ?string $paymentSignedFileName = null;
#[ORM\Column(nullable: true)]
private ?int $paymentSignedFileSize = null;
#[ORM\Column(nullable: true)]
private ?\DateTimeImmutable $updateAt = null;
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@@ -138,4 +160,117 @@ class ContratsPayments
return $this; return $this;
} }
/**
* @return \DateTimeImmutable|null
*/
public function getUpdateAt(): ?\DateTimeImmutable
{
return $this->updateAt;
}
/**
* @return File|null
*/
public function getPaymentFile(): ?File
{
return $this->paymentFile;
}
/**
* @return string|null
*/
public function getPaymentFileName(): ?string
{
return $this->paymentFileName;
}
/**
* @return int|null
*/
public function getPaymentFileSize(): ?int
{
return $this->paymentFileSize;
}
/**
* @return File|null
*/
public function getPaymentSignedFile(): ?File
{
return $this->paymentSignedFile;
}
/**
* @return string|null
*/
public function getPaymentSignedFileName(): ?string
{
return $this->paymentSignedFileName;
}
/**
* @return int|null
*/
public function getPaymentSignedFileSize(): ?int
{
return $this->paymentSignedFileSize;
}
/**
* @param \DateTimeImmutable|null $updateAt
*/
public function setUpdateAt(?\DateTimeImmutable $updateAt): void
{
$this->updateAt = $updateAt;
}
/**
* @param File|null $paymentFile
*/
public function setPaymentFile(?File $paymentFile): void
{
$this->paymentFile = $paymentFile;
}
/**
* @param string|null $paymentFileName
*/
public function setPaymentFileName(?string $paymentFileName): void
{
$this->paymentFileName = $paymentFileName;
}
/**
* @param int|null $paymentFileSize
*/
public function setPaymentFileSize(?int $paymentFileSize): void
{
$this->paymentFileSize = $paymentFileSize;
}
/**
* @param File|null $paymentSignedFile
*/
public function setPaymentSignedFile(?File $paymentSignedFile): void
{
$this->paymentSignedFile = $paymentSignedFile;
}
/**
* @param string|null $paymentSignedFileName
*/
public function setPaymentSignedFileName(?string $paymentSignedFileName): void
{
$this->paymentSignedFileName = $paymentSignedFileName;
}
/**
* @param int|null $paymentSignedFileSize
*/
public function setPaymentSignedFileSize(?int $paymentSignedFileSize): void
{
$this->paymentSignedFileSize = $paymentSignedFileSize;
}
} }

195
src/Service/Pdf/PlPdf.php Normal file
View File

@@ -0,0 +1,195 @@
<?php
namespace App\Service\Pdf;
use App\Entity\Contrats;
use App\Entity\ContratsPayments;
use App\Entity\Devis;
use Fpdf\Fpdf;
use Symfony\Component\HttpKernel\KernelInterface;
class PlPdf extends Fpdf
{
private ContratsPayments $contratsPayments;
private string $logo;
private bool $isExtraPage = false;
private Contrats $contrat;
public function __construct(KernelInterface $kernel, ContratsPayments $contratsPayments,Contrats $contrats, $orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
$this->contratsPayments = $contratsPayments;
$this->contrat = $contrats;
$this->logo = $kernel->getProjectDir()."/public/provider/images/favicon.png";
$this->AliasNbPages();
$this->SetAutoPageBreak(true, 35);
}
/**
* Convertit l'UTF-8 en Windows-1252 pour FPDF et gère l'Euro
*/
private function clean(?string $text): string
{
if (!$text) return '';
$text = iconv('UTF-8', 'windows-1252//TRANSLIT//IGNORE', $text);
return str_replace('€', chr(128), $text);
}
/**
* Helper pour afficher le symbole Euro proprement
*/
private function euro(): string
{
return ' ' . chr(128);
}
public function Header()
{
if ($this->page > 0 && !$this->isExtraPage) {
// --- LOGO ET INFOS SOCIÉTÉ ---
$this->SetY(10);
if (file_exists($this->logo)) {
$this->Image($this->logo, 10, 10, 15); // Un peu plus grand pour le reçu
$this->SetX(28);
}
$this->SetFont('Arial', 'B', 14);
$this->SetTextColor(37, 99, 235); // Bleu Ludikevent
$this->Cell(100, 7, $this->clean('Lilian SEGARD - Ludik Event'), 0, 0, 'L');
// BADGE "REÇU" À DROITE
$this->SetFont('Arial', 'B', 11);
$this->SetFillColor(37, 99, 235);
$this->SetTextColor(255, 255, 255);
$this->SetX(150);
$this->Cell(50, 8, $this->clean('REÇU DE PAIEMENT'), 0, 1, 'C', true);
// COORDONNÉES SOUS LE NOM
$this->SetY(17);
$this->SetX(28);
$this->SetFont('Arial', '', 8);
$this->SetTextColor(80, 80, 80);
$this->Cell(0, 4, $this->clean('SIRET : 930 488 408 00012 | 6 Rue du Château, 02800 Danizy'), 0, 1, 'L');
$this->SetX(28);
$this->Cell(0, 4, $this->clean('Tél. : 06 14 17 24 47 | contact@ludikevent.fr'), 0, 1, 'L');
$this->Ln(12);
// --- TITRE DU DOCUMENT ET RÉFÉRENCE ---
$this->SetFont('Arial', 'B', 15);
$this->SetTextColor(17, 24, 39); // Gris très foncé/Noir
$this->Cell(0, 10, $this->clean('Confirmation de règlement'), 0, 1, 'L');
$this->SetFont('Arial', 'B', 10);
$this->SetTextColor(107, 114, 128); // Gris intermédiaire
$this->Cell(0, 5, $this->clean('Référence Réservation : #' . $this->contrat->getNumReservation()), 0, 1, 'L');
// LIGNE DE SÉPARATION DESIGN
$this->Ln(2);
$this->SetDrawColor(37, 99, 235);
$this->SetLineWidth(0.8);
$this->Line(10, $this->GetY(), 40, $this->GetY()); // Petite ligne bleue
$this->SetDrawColor(229, 231, 235); // Gris clair
$this->SetLineWidth(0.2);
$this->Line(40, $this->GetY(), 200, $this->GetY()); // Ligne grise qui continue
$this->Ln(5);
}
}
public function generate(): string
{
$this->AddPage();
$this->renderContent();
return $this->Output('S');
}
private function renderContent(): void
{
// --- 1. BLOC INFO PAIEMENT (Le "Reçu") ---
$this->SetY(55);
$this->SetFont('Arial', 'B', 11);
$this->SetFillColor(245, 245, 255); // Bleu très léger
$this->SetTextColor(37, 99, 235);
$this->Cell(0, 10, $this->clean(" DÉTAILS DU RÈGLEMENT"), 0, 1, 'L', true);
$this->Ln(4);
$this->SetFont('Arial', '', 10);
$this->SetTextColor(50, 50, 50);
// Tableau à deux colonnes pour les infos
$startY = $this->GetY();
$this->SetFont('Arial', 'B', 10);
$this->Cell(50, 8, $this->clean("Montant réglé :"), 0, 0);
$this->SetFont('Arial', 'B', 12); $this->SetTextColor(22, 163, 74); // Vert succès
$this->Cell(0, 8, number_format($this->contratsPayments->getAmount(), 2) . $this->euro(), 0, 1);
$this->SetTextColor(50, 50, 50);
$this->SetFont('Arial', 'B', 10);
$this->Cell(50, 8, $this->clean("Date du paiement :"), 0, 0);
$this->SetFont('Arial', '', 10);
$this->Cell(0, 8, $this->contratsPayments->getValidateAt()->format('d/m/Y H:i'), 0, 1);
$this->SetFont('Arial', 'B', 10);
$this->Cell(50, 8, $this->clean("Mode de règlement :"), 0, 0);
$this->SetFont('Arial', '', 10);
$this->Cell(0, 8, $this->clean($this->contratsPayments->getCard()['type'] ?? 'Carte Bancaire'), 0, 1);
$this->SetFont('Arial', 'B', 10);
$this->Cell(50, 8, $this->clean("Référence transaction :"), 0, 0);
$this->SetFont('Arial', 'I', 9);
$this->Cell(0, 8, $this->clean($this->contratsPayments->getPaymentId() ?? 'N/A'), 0, 1);
$this->Ln(10);
// --- 2. BLOC CLIENT ---
$this->SetFont('Arial', 'B', 11);
$this->SetFillColor(250, 250, 250);
$this->SetTextColor(0, 0, 0);
$this->Cell(0, 10, $this->clean(" INFORMATIONS CLIENT"), 0, 1, 'L', true);
$this->Ln(2);
$this->SetFont('Arial', '', 10);
$customer = $this->contrat->getCustomer();
$this->MultiCell(0, 6, $this->clean(
$customer->getSurname() . " " . $customer->getName() . "\n" .
$customer->getEmail() . "\n" .
"Réservation N° " . $this->contrat->getNumReservation()
), 0, 'L');
$this->Ln(15);
// --- 3. MESSAGE DE CONFIRMATION ---
$this->SetDrawColor(22, 163, 74);
$this->SetFillColor(240, 253, 244);
$this->SetLineWidth(0.2);
$this->SetFont('Arial', 'B', 10);
$this->SetTextColor(21, 128, 61);
$text = "Ce document confirme que votre paiement a bien été reçu et validé par Ludik Event.\nNous vous remercions de votre confiance.";
$this->MultiCell(0, 10, $this->clean($text), 1, 'C', true);
// --- 4. SIGNATURE / CACHET ---
$this->SetY(-60);
$this->SetFont('Arial', 'B', 9);
$this->SetTextColor(0, 0, 0);
$this->Cell(0, 5, $this->clean('{{Sign;type=signature;role=Ludikevent;height=120;width=236}}'), 0, 1, 'C');
}
public function Footer(): void
{
// Positionnement à 1,5 cm du bas
$this->SetY(-15);
$this->SetFont('Arial', 'I', 7);
$this->SetTextColor(150, 150, 150);
// Texte gauche : Identification du devis
$this->Cell(0, 10, $this->clean('Confirmation de paiement Ludik Event - Lilian SEGARD - SIRET 93048840800012'), 0, 0, 'L');
// Numérotation des pages à droite
$this->SetTextColor(150, 150, 150);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 10, 'Page ' . $this->PageNo() . '/{nb}', 0, 0, 'R');
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Service\Signature; namespace App\Service\Signature;
use App\Entity\Contrats; use App\Entity\Contrats;
use App\Entity\ContratsPayments;
use App\Entity\CustomerOrder; use App\Entity\CustomerOrder;
use App\Entity\Devis; use App\Entity\Devis;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@@ -269,4 +270,33 @@ class Client
$submiter = $this->getSubmiter($signId); $submiter = $this->getSubmiter($signId);
return $submiter['uuid']; // numéro de signature; return $submiter['uuid']; // numéro de signature;
} }
public function autoSignConfirmedPayment(ContratsPayments $contratsPayments) {
$relativeFileUrl = $this->storage->resolveUri($contratsPayments, 'paymentFile');
$fileUrl = $this->baseUrl . $relativeFileUrl;
$submission = $this->docuseal->createSubmissionFromPdf([
'name' => 'Confirmaton de paiement N°' . $contratsPayments->getPaymentId(), // Correction : getNum()
'send_email' => true,
'documents' => [
[
'name' => 'confirmation_paiement_' . $contratsPayments->getId() . '.pdf', // Correction : getNum()
'file' => $fileUrl,
],
],
'submitters' => [
[
'role' => 'Ludikevent',
'email' => 'contact@ludikevent.fr',
'completed' => true,
'fields' => [
['name'=>'Sign','default_value'=>$this->logoBase64()]
]
],
],
]);
$sub = $this->docuseal->getSubmission($submission['id']);
sleep(5);
return $sub['documents'][0]['url'];
}
} }

View File

@@ -394,7 +394,6 @@ class Client
*/ */
public function checkWebhooks(\Symfony\Component\HttpFoundation\Request $request, string $configName): bool public function checkWebhooks(\Symfony\Component\HttpFoundation\Request $request, string $configName): bool
{ {
return true;
// 1. Récupération de la config (Secret de signature) // 1. Récupération de la config (Secret de signature)
$config = $this->em->getRepository(StripeConfig::class)->findOneBy(['name' => $configName]); $config = $this->em->getRepository(StripeConfig::class)->findOneBy(['name' => $configName]);

View File

@@ -362,6 +362,7 @@
<th class="px-8 py-4 text-[9px] font-black uppercase text-slate-400 tracking-widest">Type / Objet</th> <th class="px-8 py-4 text-[9px] font-black uppercase text-slate-400 tracking-widest">Type / Objet</th>
<th class="px-8 py-4 text-[9px] font-black uppercase text-slate-400 tracking-widest">Méthode</th> <th class="px-8 py-4 text-[9px] font-black uppercase text-slate-400 tracking-widest">Méthode</th>
<th class="px-8 py-4 text-[9px] font-black uppercase text-slate-400 tracking-widest text-right">Montant</th> <th class="px-8 py-4 text-[9px] font-black uppercase text-slate-400 tracking-widest text-right">Montant</th>
<th class="px-8 py-4 text-[9px] font-black uppercase text-slate-400 tracking-widest text-right">Confirmation de paiement</th>
<th class="px-8 py-4 text-[9px] font-black uppercase text-slate-400 tracking-widest text-center">Statut</th> <th class="px-8 py-4 text-[9px] font-black uppercase text-slate-400 tracking-widest text-center">Statut</th>
</tr> </tr>
</thead> </thead>
@@ -396,6 +397,24 @@
<td class="px-8 py-5 text-right font-black text-slate-900 italic text-sm"> <td class="px-8 py-5 text-right font-black text-slate-900 italic text-sm">
{{ pay.amount|number_format(2, ',', ' ') }} {{ pay.amount|number_format(2, ',', ' ') }}
</td> </td>
<td class="px-8 py-5 text-right">
<div class="flex justify-end">
<a target="_blank" href="{{ path('gestion_contrat_view', {num: contrat.numReservation, idPaymentPdf: pay.id}) }}"
class="group relative flex items-center justify-center w-10 h-10 bg-blue-50 hover:bg-blue-600 text-blue-600 hover:text-white rounded-full transition-all duration-300 shadow-sm"
title="Télécharger le reçu">
{# Icône de téléchargement épurée #}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 stroke-[2.5] transition-transform group-hover:scale-110" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
</svg>
{# Tooltip (optionnel) #}
<span class="absolute -top-8 scale-0 group-hover:scale-100 transition-all bg-slate-800 text-white text-[10px] px-2 py-1 rounded font-bold uppercase tracking-tighter">
PDF
</span>
</a>
</div>
</td>
<td class="px-8 py-5 text-center"> <td class="px-8 py-5 text-center">
<div class="inline-flex items-center gap-1.5 text-green-600"> <div class="inline-flex items-center gap-1.5 text-green-600">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path></svg> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path></svg>