baseUrl = $_ENV['SIGN_URL']; // L'URL API est le point d'entrée pour le SDK Docuseal $apiUrl = rtrim("https://signature.esy-web.dev", '/') . '/api'; $this->docuseal = $docuseal ?? new \Docuseal\Api($key, $apiUrl); $this->logo = $kernel->getProjectDir()."/sign_ludikevent.jpeg"; } /** * Crée une soumission pour un Devis */ public function createSubmissionDevis(Devis $devis): string { // Si aucune signature n'est lancée, on initialise la soumission if ($devis->getSignatureId() === null) { // URL où le client sera redirigé après signature $completedRedirectUrl = $this->baseUrl . $this->urlGenerator->generate( 'app_sign_complete', ['type' => 'devis', 'id' => $devis->getId()] ); // Récupération du fichier via VichUploader (champ devisDocuSealFile) $relativeFileUrl = $this->storage->resolveUri($devis, 'devisDocuSealFile'); $fileUrl = $this->baseUrl . $relativeFileUrl; $submission = $this->docuseal->createSubmissionFromPdf([ 'name' => 'Devis N°' . $devis->getNum(), // Correction : getNum() 'completed_redirect_url' => $completedRedirectUrl, 'send_email' => true, 'documents' => [ [ 'name' => 'devis_' . $devis->getNum() . '.pdf', // Correction : getNum() 'file' => $fileUrl, ], ], 'submitters' => [ [ 'role' => 'Ludikevent', 'email' => 'contact@ludikevent.fr', 'completed' => true, 'fields' => [ ['name'=>'Sign','default_value'=>$this->logoBase64()] ] ], [ 'role' => 'Client', 'email' => $devis->getCustomer()->getEmail(), 'name' => $devis->getCustomer()->getSurname() . ' ' . $devis->getCustomer()->getName(), 'fields' => [ ['name'=>'cgv','default_value'=>false], ['name'=>'assurance','default_value'=>false], ['name'=>'securite','default_value'=>false], ['name'=>'arrhes','default_value'=>false], ], 'metadata' => [ 'id' => $devis->getId(), 'type' => 'devis' ] ], ], ]); // Stockage de l'ID submitter de Docuseal dans ton entité $devis->setSignatureId($submission['submitters'][1]['id']); $this->entityManager->persist($devis); $this->entityManager->flush(); } return $this->getLinkSign($devis->getSignatureId()); } /** * Retourne l'URL de signature publique pour le client */ public function getLinkSign(?string $submitterId): string { if (!$submitterId) { throw new \InvalidArgumentException("ID Submitter absent."); } $submissionData = $this->docuseal->getSubmitter($submitterId); return rtrim("https://signature.esy-web.dev", '/') . "/s/" . $submissionData['slug']; } /** * Récupère l'état d'un signataire (pour vérifier si c'est signé) */ public function getSubmiter(?string $submitterId): array { return $this->docuseal->getSubmitter($submitterId); } public function status(): bool { try { $this->docuseal->listTemplates(); return true; } catch (\Throwable $e) { return false; } } /** * Récupère le fichier logo et le convertit en chaîne Base64 * Utile pour l'intégration directe dans certains flux HTML ou API */ private function logoBase64(): ?string { // Vérifie si le fichier existe pour éviter une erreur if (!file_exists($this->logo)) { return null; } // Lecture du contenu du fichier $binaryData = file_get_contents($this->logo); // Récupération de l'extension pour le type MIME (png, jpg, etc.) $extension = pathinfo($this->logo, PATHINFO_EXTENSION); // Encodage en Base64 $base64 = base64_encode($binaryData); // Retourne le format complet data:image/... return 'data:image/' . $extension . ';base64,' . $base64; } public function getSubmition(mixed $submission_id) { return $this->docuseal->getSubmission($submission_id); } public function cancelSign(?string $getSignatureId) { $result = $this->docuseal->getSubmitter($getSignatureId); $this->docuseal->archiveSubmission($result['submission_id']); } public function createSubmissionContrat(Contrats $devis): string { // Si aucune signature n'est lancée, on initialise la soumission if ($devis->getSignID() === null) { // URL où le client sera redirigé après signature $completedRedirectUrl = $this->baseUrl . $this->urlGenerator->generate( 'app_sign_complete', ['type' => 'contrat', 'id' => $devis->getId()] ); // Récupération du fichier via VichUploader (champ devisDocuSealFile) $relativeFileUrl = $this->storage->resolveUri($devis, 'devisDocuSealFile'); $fileUrl = $this->baseUrl . $relativeFileUrl; $submission = $this->docuseal->createSubmissionFromPdf([ 'name' => 'Contrat N°' . $devis->getNumReservation(), // Correction : getNum() 'completed_redirect_url' => $completedRedirectUrl, 'send_email' => true, 'documents' => [ [ 'name' => 'contrat_' . $devis->getNumReservation() . '.pdf', // Correction : getNum() 'file' => $fileUrl, ], ], 'submitters' => [ [ 'role' => 'Ludikevent', 'email' => 'contact@ludikevent.fr', 'completed' => true, 'fields' => [ ['name'=>'Sign','default_value'=>$this->logoBase64()] ] ], [ 'role' => 'Client', 'email' => $devis->getCustomer()->getEmail(), 'name' => $devis->getCustomer()->getSurname() . ' ' . $devis->getCustomer()->getName(), 'fields' => [ ['name'=>'cgv','default_value'=>false], ['name'=>'assurance','default_value'=>false], ['name'=>'securite','default_value'=>false], ['name'=>'arrhes','default_value'=>false], ], 'metadata' => [ 'id' => $devis->getId(), 'type' => 'contrat' ] ], ], ]); // Stockage de l'ID submitter de Docuseal dans ton entité $devis->setSignID($submission['submitters'][1]['id']); $this->entityManager->flush(); } return $this->getLinkSign($devis->getSignID()); } public function eventSign(object $contrat): array { $events = []; if ($contrat instanceof Contrats) { $signId = $contrat->getSignID(); if (!$signId) return []; // Sécurité si pas d'ID de signature $submiter = $this->getSubmiter($signId); // Vérifier si submission_events existe pour éviter une erreur undefined index if (!isset($submiter['submission_events'])) return []; foreach ($submiter['submission_events'] as $event) { $label = match($event['event_type']) { 'view_form' => "Contrat consulté", 'start_form' => "Début de procédure", 'complete_form' => "Contrat signé", 'decline_form' => "Signature refusée", default => null }; if ($label) { $events[] = [ 'event_type' => $label, 'event_timestamp' => new \DateTimeImmutable($event['event_timestamp']) ]; } } } return $events; } public function signedData(Contrats $contrat) : string { $signId = $contrat->getSignID(); if (!$signId) return []; // Sécurité si pas d'ID de signature $submiter = $this->getSubmiter($signId); 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' => 'Confirmation de paiement N°' . $contratsPayments->getPaymentId(), 'send_email' => true, 'documents' => [ [ 'name' => 'confirmation_paiement_' . $contratsPayments->getId() . '.pdf', 'file' => $fileUrl, ], ], 'submitters' => [ [ 'role' => 'Ludikevent', 'email' => 'contact@ludikevent.fr', 'completed' => true, 'fields' => [ ['name' => 'Sign', 'default_value' => $this->logoBase64()] ] ], ], ]); // --- SYSTÈME DE VÉRIFICATION DYNAMIQUE --- $maxAttempts = 10; // On essaie 10 fois maximum $attempts = 0; $documentUrl = null; while ($attempts < $maxAttempts) { $sub = $this->docuseal->getSubmission($submission['id']); // Vérification si le document et son URL sont prêts if (!empty($sub['documents'][0]['url'])) { $documentUrl = $sub['documents'][0]['url']; break; // On sort de la boucle dès que c'est prêt } $attempts++; usleep(500000); // Attend 0.5 seconde avant de réessayer (plus rapide que sleep(1)) } if (!$documentUrl) { throw new \Exception("Docuseal n'a pas généré le document signé à temps pour le paiement " . $contratsPayments->getId()); } return $documentUrl; } public function createSubmissionEtatLieux(EtatLieux $etatLieux): void { // Si déjà initié, on arrête (ou on pourrait retourner les liens existants) if ($etatLieux->getSignIdCustomer()) { return; } $contrat = $etatLieux->getContrat(); $customer = $contrat->getCustomer(); // Prestataire or Admin $prestataireEmail = 'contact@ludikevent.fr'; if ($etatLieux->getPrestataire()) { $prestataireEmail = $etatLieux->getPrestataire()->getEmail(); } elseif ($etatLieux->getAccount()) { $prestataireEmail = $etatLieux->getAccount()->getEmail(); } // URL où on redirige après signature $completedRedirectUrl = $this->baseUrl . $this->urlGenerator->generate('etl_mission_signed_entry_state', ['id' => $contrat->getId()]); // Récupération du fichier PDF EDL (Non signé) $relativeFileUrl = $this->storage->resolveUri($etatLieux, 'etatLieuxUnsignFile'); $fileUrl = $this->baseUrl . $relativeFileUrl; $submission = $this->docuseal->createSubmissionFromPdf([ 'name' => 'Etat des Lieux - Contrat #' . $contrat->getNumReservation(), 'completed_redirect_url' => $completedRedirectUrl, 'send_email' => true, // Envoi email aux deux parties 'documents' => [ [ 'name' => 'edl_' . $contrat->getNumReservation() . '.pdf', 'file' => $fileUrl, ], ], 'submitters' => [ [ 'role' => 'Ludikevent', // Prestataire 'email' => $prestataireEmail, ], [ 'role' => 'Client', 'email' => $customer->getEmail(), 'name' => $customer->getSurname() . ' ' . $customer->getName(), ], ], ]); // Mapping des IDs foreach ($submission['submitters'] as $submitter) { if ($submitter['role'] === 'Ludikevent') { $etatLieux->setSignIdDelivery($submitter['id']); } elseif ($submitter['role'] === 'Client') { $etatLieux->setSignIdCustomer($submitter['id']); } } $this->entityManager->persist($etatLieux); $this->entityManager->flush(); } public function getSigningUrl(EtatLieux $etatLieux, string $role): ?string { $submitterId = match ($role) { 'Ludikevent', 'Prestataire' => $etatLieux->getSignIdDelivery(), 'Client' => $etatLieux->getSignIdCustomer(), default => null }; if (!$submitterId) { return null; } return $this->getLinkSign($submitterId); } }