```
✨ feat(dons): Ajoute la fonctionnalité de dons avec Stripe et reçus PDF.
Ajoute une page de dons avec formulaire, intégration Stripe, webhooks,
génération de reçus PDF et envoi de mails de confirmation. Ajoute aussi
gestion des erreurs 404/500.
```
This commit is contained in:
5
.env
5
.env
@@ -51,3 +51,8 @@ VITE_LOAD=0
|
||||
REDIS_DSN="redis://redis:6379"
|
||||
REAL_MAIL=0
|
||||
PATH_URL=https://esyweb.local
|
||||
|
||||
STRIPE_PK=pk_test_51SUA22173W4aeFB1nO6oFfDZ12HOTffDKtCshhZ8rkUg6kUO2ZaQC0tK72rhE79Tr8treeHX9KMcZtvcQZ0X8VSm00Q6GQ365V
|
||||
STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR
|
||||
STRIPE_WEBHOOKS_SIGN=whsec_0DOZJAwgMwkcHl2RWXI8h8YItj9q7v3A
|
||||
DEV_URL=https://3ea1cf1b1555.ngrok-free.app
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
VAULT_ADDR=http://127.0.0.1:8200
|
||||
VAULT_TOKEN=hvs.QLpUdiptXtSPo5Qf7i2nn2Xz
|
||||
APP_DEBUG=true
|
||||
STRIPE_PK=pk_live_51SUA1rP4ub49xK2ThoRH8efqGYNi1hrcWMzrqmDtJpMv12cmTzLa8ncJLUKLbOQNZTkm1jgptLfwt4hxEGqkVsHB00AK3ieZNl
|
||||
STRIPE_SK=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
|
||||
STRIPE_WEBHOOKS_SIGN=whsec_wNHtgjypqbfP7erAqifCOzZvW8kW9oB7
|
||||
MAILER_DSN=ses+smtp://AKIAWTT2T22CWBRBBDYN:BBdgb6KxRQ8mNcpWFJsZCJxbSGNdgLhKFiITMErfBlQP@default?region=eu-west-3
|
||||
dest: "{{ path }}/.env.local"
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
23
assets/PaymentForm.js
Normal file
23
assets/PaymentForm.js
Normal file
@@ -0,0 +1,23 @@
|
||||
export class PaymentForm extends HTMLFormElement{
|
||||
connectedCallback() {
|
||||
this.addEventListener('submit',(e)=>{
|
||||
e.preventDefault();
|
||||
|
||||
let inputs ={};
|
||||
this.querySelectorAll('input').forEach(input=>{
|
||||
inputs[input.name] = input.value
|
||||
})
|
||||
this.querySelectorAll('textarea').forEach(input=>{
|
||||
inputs[input.name] = input.value
|
||||
})
|
||||
|
||||
fetch("/dons",{
|
||||
method:"POST",
|
||||
body:JSON.stringify(inputs)
|
||||
}).then(rslt=>rslt.json())
|
||||
.then((reslt)=>{
|
||||
console.log(reslt)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import './app.scss'
|
||||
import * as Turbo from "@hotwired/turbo"
|
||||
|
||||
import {PaymentForm} from './PaymentForm'
|
||||
/**
|
||||
* Fonction générique pour basculer la visibilité d'un menu déroulant.
|
||||
* @param {HTMLElement} button - Le bouton qui déclenche l'action.
|
||||
@@ -79,6 +80,8 @@ function initializeUI() {
|
||||
|
||||
// --- INITIALISATION DES COMPOSANTS APRÈS TURBO/CHARGEMENT ---
|
||||
document.addEventListener('DOMContentLoaded', ()=>{
|
||||
customElements.define('payment-don',PaymentForm,{extends:'form'})
|
||||
|
||||
initializeUI()
|
||||
const env = document.querySelector('meta[name="env"]')
|
||||
if(env.getAttribute('content') == "prod") {
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"setasign/fpdi": "^2.6.4",
|
||||
"spatie/mjml-php": "^1.2.5",
|
||||
"stancer/stancer": ">=2.0.1",
|
||||
"stripe/stripe-php": "^18.2",
|
||||
"symfony/amazon-mailer": "7.3.*",
|
||||
"symfony/asset": "7.3.*",
|
||||
"symfony/asset-mapper": "7.3.*",
|
||||
|
||||
61
composer.lock
generated
61
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6c414bbc5665617d98a78419d58c159b",
|
||||
"content-hash": "fe5cfb5ef73b767b1d84ebbc20c2c1e4",
|
||||
"packages": [
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
@@ -8000,6 +8000,65 @@
|
||||
},
|
||||
"time": "2024-11-15T17:47:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "stripe/stripe-php",
|
||||
"version": "v18.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/stripe/stripe-php.git",
|
||||
"reference": "0acd7bdac84ad0f940d5da30c417170ce59b4fbc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/0acd7bdac84ad0f940d5da30c417170ce59b4fbc",
|
||||
"reference": "0acd7bdac84ad0f940d5da30c417170ce59b4fbc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=5.6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "3.72.0",
|
||||
"phpstan/phpstan": "^1.2",
|
||||
"phpunit/phpunit": "^5.7 || ^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Stripe\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Stripe and contributors",
|
||||
"homepage": "https://github.com/stripe/stripe-php/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Stripe PHP Library",
|
||||
"homepage": "https://stripe.com/",
|
||||
"keywords": [
|
||||
"api",
|
||||
"payment processing",
|
||||
"stripe"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/stripe/stripe-php/issues",
|
||||
"source": "https://github.com/stripe/stripe-php/tree/v18.2.0"
|
||||
},
|
||||
"time": "2025-11-05T22:59:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/amazon-mailer",
|
||||
"version": "v7.3.0",
|
||||
|
||||
32
migrations/Version20251118192837.php
Normal file
32
migrations/Version20251118192837.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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 Version20251118192837 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('CREATE TABLE dons (id SERIAL NOT NULL, name VARCHAR(255) DEFAULT NULL, email VARCHAR(255) NOT NULL, message TEXT DEFAULT NULL, amount DOUBLE PRECISION NOT NULL, PRIMARY KEY(id))');
|
||||
}
|
||||
|
||||
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('DROP TABLE dons');
|
||||
}
|
||||
}
|
||||
56
src/Controller/DonsController.php
Normal file
56
src/Controller/DonsController.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Dto\Contact\ContactType;
|
||||
use App\Dto\Contact\DtoContact;
|
||||
use App\Entity\Account;
|
||||
use App\Entity\AccountResetPasswordRequest;
|
||||
use App\Form\RequestPasswordConfirmType;
|
||||
use App\Form\RequestPasswordRequestType;
|
||||
use App\Service\Mailer\Mailer;
|
||||
use App\Service\Payments\PaymentClient;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordEvent;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
use Twig\Environment;
|
||||
|
||||
class DonsController extends AbstractController
|
||||
{
|
||||
|
||||
#[Route(path: '/dons', name: 'app_dons', options: ['sitemap' => false], methods: ['GET','POST'])]
|
||||
public function index(Request $request,PaymentClient $paymentClient): Response
|
||||
{
|
||||
if($request->query->has('success')) {
|
||||
$dons = $request->getSession()->get('dons');
|
||||
|
||||
$request->getSession()->remove('dons');
|
||||
return $this->render('dons_success.twig',[
|
||||
'dons' => $dons,
|
||||
]);
|
||||
}
|
||||
if($request->isMethod('POST')) {
|
||||
|
||||
$content = $request->request->all();
|
||||
|
||||
$payment = $paymentClient->paymentDon($content);
|
||||
|
||||
$content['id_payment'] = $payment->id;
|
||||
|
||||
$request->getSession()->set('dons',$content);
|
||||
return new RedirectResponse($payment->url);
|
||||
}
|
||||
|
||||
return $this->render('dons.twig',[
|
||||
]);
|
||||
}
|
||||
}
|
||||
79
src/Controller/WebhooksController.php
Normal file
79
src/Controller/WebhooksController.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Dto\Contact\ContactType;
|
||||
use App\Dto\Contact\DtoContact;
|
||||
use App\Entity\Account;
|
||||
use App\Entity\AccountResetPasswordRequest;
|
||||
use App\Entity\Dons;
|
||||
use App\Form\RequestPasswordConfirmType;
|
||||
use App\Form\RequestPasswordRequestType;
|
||||
use App\Service\Mailer\Mailer;
|
||||
use App\Service\Payments\PaymentClient;
|
||||
use App\Service\Pdf\DonReceiptGenerator;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent;
|
||||
use App\Service\ResetPassword\Event\ResetPasswordEvent;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
use Twig\Environment;
|
||||
|
||||
class WebhooksController extends AbstractController
|
||||
{
|
||||
|
||||
#[Route(path: '/webhooks', name: 'app_webhooks', options: ['sitemap' => false], methods: ['POST'])]
|
||||
public function index(DonReceiptGenerator $donReceiptGenerator,Request $request,PaymentClient $paymentClient,Mailer $mailer,EntityManagerInterface $entityManager,): Response
|
||||
{
|
||||
$content = $request->getContent();
|
||||
if($paymentClient->validateWebhooks($content)) {
|
||||
$content = json_decode($content);
|
||||
$object = $content->data->object;
|
||||
$metadata = $object->metadata;
|
||||
if($object->status == "complete") {
|
||||
$dons = new Dons();
|
||||
$dons->setName($metadata->name);
|
||||
$dons->setEmail($metadata->email);
|
||||
$dons->setAmount($metadata->amount);
|
||||
$dons->setMessage($metadata->message);
|
||||
$entityManager->persist($dons);
|
||||
$pdfGenerator = new DonReceiptGenerator();
|
||||
|
||||
$v= new \DateTime();
|
||||
$donationData = [
|
||||
'name' => $metadata->name,
|
||||
'email' => $metadata->email,
|
||||
'amount' => $metadata->amount,
|
||||
'date' => $v->format('Y-m-d'),
|
||||
'message' => $metadata->message ?? null,
|
||||
];
|
||||
$files = [];
|
||||
$pdfContent = $pdfGenerator->generate(
|
||||
$donationData,
|
||||
'recu_don_E-Cosplay.pdf',
|
||||
'S'
|
||||
);
|
||||
$files[] = new DataPart($pdfContent,'recu_don_E-Cosplay.pdf','application/pdf');
|
||||
|
||||
$mailer->send($metadata->email,$metadata->name,'[E-Cosplay] - Confirmation de votre don de '.$metadata->amount." €","mails/dons.twig",[
|
||||
'don' => $dons,
|
||||
],$files);
|
||||
|
||||
$mailer->send($metadata->email,$metadata->name,'[E-Cosplay] - Confirmation d\'un don de '.$metadata->amount." €","mails/dons_new.twig",[
|
||||
'don' => $dons,
|
||||
]);
|
||||
|
||||
$entityManager->flush();
|
||||
}
|
||||
}
|
||||
return $this->json([]);
|
||||
}
|
||||
}
|
||||
81
src/Entity/Dons.php
Normal file
81
src/Entity/Dons.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\DonsRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: DonsRepository::class)]
|
||||
class Dons
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $email = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $message = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $amount = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(?string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(string $email): static
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMessage(): ?string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function setMessage(?string $message): static
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAmount(): ?float
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
public function setAmount(float $amount): static
|
||||
{
|
||||
$this->amount = $amount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
43
src/EventSubscriber/ErrorListener.php
Normal file
43
src/EventSubscriber/ErrorListener.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use Presta\SitemapBundle\Event\SitemapPopulateEvent;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Twig\Environment;
|
||||
|
||||
#[AsEventListener(event: ExceptionEvent::class, method: 'onException', priority: 10)]
|
||||
class ErrorListener
|
||||
{
|
||||
public function __construct(private readonly Environment $environment)
|
||||
{
|
||||
}
|
||||
|
||||
public function onException(ExceptionEvent $exceptionEvent) {
|
||||
$exception = $exceptionEvent->getThrowable();
|
||||
|
||||
if($exception instanceof NotFoundHttpException) {
|
||||
$response = new Response($this->environment->render('error/404.twig',[
|
||||
'no_index' => true,
|
||||
]));
|
||||
$response->setStatusCode(Response::HTTP_NOT_FOUND);
|
||||
$exceptionEvent->setResponse($response);
|
||||
$exceptionEvent->stopPropagation();
|
||||
} else {
|
||||
$response = new Response($this->environment->render('error/error.twig',[
|
||||
'no_index' => true,
|
||||
]));
|
||||
|
||||
$response->setStatusCode(Response::HTTP_NOT_FOUND);
|
||||
$exceptionEvent->setResponse($response);
|
||||
$exceptionEvent->stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,15 @@ class SitemapSubscriber
|
||||
}
|
||||
$urlContainer->addUrl($urlEvents, 'default');
|
||||
|
||||
$urlDons = new UrlConcrete($urlGenerator->generate('app_dons', [], UrlGeneratorInterface::ABSOLUTE_URL));
|
||||
$urlDons = new GoogleImageUrlDecorator($urlDons);
|
||||
$urlDons->addImage(new GoogleImage($this->cacheManager->resolve('assets/images/logo.jpg','webp')));
|
||||
$urlDons = new GoogleMultilangUrlDecorator($urlDons);
|
||||
foreach ($langs as $lang) {
|
||||
$urlDons->addLink($urlGenerator->generate('app_dons',['lang'=>$lang], UrlGeneratorInterface::ABSOLUTE_URL), $lang);
|
||||
}
|
||||
$urlContainer->addUrl($urlDons, 'default');
|
||||
|
||||
$urlAbout = new UrlConcrete($urlGenerator->generate('app_about', [], UrlGeneratorInterface::ABSOLUTE_URL));
|
||||
$decoratedUrlAbout = new GoogleImageUrlDecorator($urlAbout);
|
||||
$decoratedUrlAbout->addImage(new GoogleImage($this->cacheManager->resolve('assets/images/logo.jpg','webp')));
|
||||
|
||||
43
src/Repository/DonsRepository.php
Normal file
43
src/Repository/DonsRepository.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Dons;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Dons>
|
||||
*/
|
||||
class DonsRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Dons::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Dons[] Returns an array of Dons objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('d')
|
||||
// ->andWhere('d.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('d.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Dons
|
||||
// {
|
||||
// return $this->createQueryBuilder('d')
|
||||
// ->andWhere('d.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
58
src/Service/Payments/PaymentClient.php
Normal file
58
src/Service/Payments/PaymentClient.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Payments;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class PaymentClient
|
||||
{
|
||||
|
||||
private string $url;
|
||||
|
||||
public function __construct(private readonly RequestStack $requestStack, private readonly UrlGeneratorInterface $urlGenerator, private readonly EntityManagerInterface $em)
|
||||
{
|
||||
if($_ENV['APP_ENV'] == "prod")
|
||||
$this->url = $this->requestStack->getCurrentRequest()->getSchemeAndHttpHost();
|
||||
else
|
||||
$this->url = $_ENV['DEV_URL'];
|
||||
}
|
||||
|
||||
public function paymentDon(array $content)
|
||||
{
|
||||
$stripe = new \Stripe\StripeClient($_ENV['STRIPE_SK']);
|
||||
|
||||
$checkoutSession = $stripe->checkout->sessions->create([
|
||||
'customer_email' => $content['email'],
|
||||
'line_items' => [[
|
||||
'price_data' => [
|
||||
'currency' => 'eur',
|
||||
'product_data' => [
|
||||
'name' => "Dons ".$content['amount']." €",
|
||||
'description' => "Dons ".$content['amount']." €",
|
||||
],
|
||||
'unit_amount' => $content['amount']*100,
|
||||
],
|
||||
'quantity' => 1,
|
||||
]],
|
||||
'metadata' => $content,
|
||||
'mode' => 'payment',
|
||||
'success_url' => $this->url.$this->urlGenerator->generate('app_dons',['success' => true]),
|
||||
]);
|
||||
|
||||
return $checkoutSession;
|
||||
}
|
||||
|
||||
public function validateWebhooks($payload)
|
||||
{
|
||||
try {
|
||||
$event = \Stripe\Event::constructFrom(
|
||||
json_decode($payload, true)
|
||||
);
|
||||
return true;
|
||||
} catch(\UnexpectedValueException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
179
src/Service/Pdf/DonReceiptGenerator.php
Normal file
179
src/Service/Pdf/DonReceiptGenerator.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Pdf;
|
||||
|
||||
use Fpdf\Fpdf;
|
||||
|
||||
class DonReceiptGenerator extends Fpdf
|
||||
{
|
||||
private array $donationData;
|
||||
|
||||
/**
|
||||
* Initialise le générateur de reçu avec les données du don.
|
||||
* @param array $data Les données du don (amount, name, date, email, message, etc.).
|
||||
*/
|
||||
public function setDonationData(array $data): void
|
||||
{
|
||||
$this->donationData = $data;
|
||||
}
|
||||
|
||||
// --- Méthodes FPDF obligatoires ou recommandées ---
|
||||
|
||||
/**
|
||||
* Entête du document (Logo, Nom de l'association).
|
||||
*/
|
||||
public function Header(): void
|
||||
{
|
||||
// Logo de l'association E-Cosplay
|
||||
// IMPORTANT : Ajustez le chemin de l'image pour qu'il soit absolu ou relatif à l'exécution du script
|
||||
// Exemple pour Symfony : $_SERVER['DOCUMENT_ROOT'] . '/assets/images/logo.jpg'
|
||||
$logoPath = $_SERVER['DOCUMENT_ROOT'] . '/assets/images/logo.jpg';
|
||||
|
||||
// Vérifie si le fichier existe avant de tenter de l'insérer
|
||||
if (file_exists($logoPath)) {
|
||||
$this->Image($logoPath, 10, 8, 30); // Position X=10, Y=8, Largeur=30mm (Hauteur auto)
|
||||
} else {
|
||||
// Optionnel : Afficher un texte si le logo n'est pas trouvé
|
||||
$this->SetFont('Arial', 'B', 10);
|
||||
$this->Cell(0, 10, utf8_decode('E-Cosplay Logo'), 0, 0, 'L');
|
||||
$this->Ln(5); // Saut de ligne après le texte du logo
|
||||
}
|
||||
|
||||
// Police de caractères (ex: Arial gras 15)
|
||||
$this->SetFont('Arial', 'B', 15);
|
||||
// Décalage à droite (après le logo si utilisé, ou ajuster)
|
||||
// Si logo utilisé, le décalage doit être plus grand
|
||||
$this->Cell(80); // Ajustez cette valeur si le logo est présent et plus grand/petit
|
||||
// Titre - Utilisation de utf8_decode()
|
||||
$this->Cell(30, 10, utf8_decode('Reçu de Don'), 0, 1, 'C');
|
||||
|
||||
// Informations de l'association
|
||||
$this->SetFont('Arial', '', 10);
|
||||
// Ligne 1: Nom de l'association
|
||||
$this->Cell(0, 5, utf8_decode('E-Cosplay'), 0, 1, 'C');
|
||||
// Ligne 2: Adresse
|
||||
$this->Cell(0, 5, utf8_decode('42 RUE DE SAINT-QUENTIN 02800 BEAUTOR'), 0, 1, 'C');
|
||||
// Ligne 3: SIREN et NAF
|
||||
$this->Cell(0, 5, utf8_decode('SIREN: 943121517 | Code NAF: 93.29Z'), 0, 1, 'C');
|
||||
|
||||
// Saut de ligne
|
||||
$this->Ln(15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pied de page du document.
|
||||
*/
|
||||
public function Footer(): void
|
||||
{
|
||||
// Positionnement à 1.5 cm du bas
|
||||
$this->SetY(-15);
|
||||
// Police Arial italique 8
|
||||
$this->SetFont('Arial', 'I', 8);
|
||||
// Numéro de page
|
||||
$this->Cell(0, 10, utf8_decode('Page ') . $this->PageNo() . '/{nb}', 0, 0, 'C');
|
||||
}
|
||||
|
||||
// --- Méthode de génération spécifique au reçu ---
|
||||
|
||||
/**
|
||||
* Génère le corps principal du reçu de don.
|
||||
*/
|
||||
public function generateReceiptBody(): void
|
||||
{
|
||||
if (empty($this->donationData)) {
|
||||
// Utilisation de utf8_decode()
|
||||
$this->Cell(0, 10, utf8_decode('Erreur: Aucune donnée de don n\'est définie.'), 0, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->SetFont('Arial', '', 12);
|
||||
|
||||
// Affichage des informations du donateur
|
||||
$this->SetTextColor(0, 0, 0); // Noir
|
||||
$this->Cell(0, 10, utf8_decode('Donateur:'), 0, 1);
|
||||
|
||||
// Récupération et décodage des données du donateur
|
||||
$name = utf8_decode($this->donationData['name'] ?? 'Non spécifié');
|
||||
$email = utf8_decode($this->donationData['email'] ?? 'Non spécifié');
|
||||
|
||||
$this->SetX(20); // Décalage pour les détails
|
||||
$this->Cell(0, 7, utf8_decode('Nom/Pseudo: ') . $name, 0, 1);
|
||||
$this->SetX(20);
|
||||
$this->Cell(0, 7, utf8_decode('E-mail: ') . $email, 0, 1);
|
||||
|
||||
$this->Ln(10);
|
||||
|
||||
// Affichage des détails du don
|
||||
$this->SetFont('Arial', 'B', 14);
|
||||
$this->SetFillColor(200, 220, 255); // Couleur de fond pour le titre
|
||||
// Utilisation de utf8_decode()
|
||||
$this->Cell(0, 10, utf8_decode('Détails de la Contribution'), 0, 1, 'L', true);
|
||||
|
||||
$this->SetFont('Arial', '', 12);
|
||||
$this->Ln(3);
|
||||
|
||||
// Montant
|
||||
$amount = $this->donationData['amount'] ?? 0;
|
||||
$formattedAmount = number_format($amount, 2, ',', '.') . ' EUR';
|
||||
|
||||
$this->SetX(20);
|
||||
$this->Cell(50, 8, utf8_decode('Montant Reçu:'), 0, 0);
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$this->Cell(0, 8, $formattedAmount, 0, 1);
|
||||
$this->SetFont('Arial', '', 12);
|
||||
|
||||
// Date
|
||||
$date = $this->donationData['date'] ?? date('Y-m-d');
|
||||
$this->SetX(20);
|
||||
$this->Cell(50, 8, utf8_decode('Date de la Transaction:'), 0, 0);
|
||||
$this->Cell(0, 8, $date, 0, 1);
|
||||
|
||||
// Message (si présent)
|
||||
if (!empty($this->donationData['message'])) {
|
||||
$message = utf8_decode($this->donationData['message']);
|
||||
$this->Ln(5);
|
||||
$this->Cell(0, 8, utf8_decode('Message du Donateur:'), 0, 1);
|
||||
$this->SetX(20);
|
||||
$this->MultiCell(0, 6, $message);
|
||||
}
|
||||
|
||||
$this->Ln(15);
|
||||
|
||||
// Mention de non-déductibilité (Crucial) - Utilisation de utf8_decode()
|
||||
$nonDeductibilityText = 'NOTE IMPORTANTE : Votre don à notre association ne vous permet pas de bénéficier d\'une réduction d\'impôt en vertu des lois fiscales actuelles.';
|
||||
|
||||
$this->SetFont('Arial', 'B', 10);
|
||||
$this->SetTextColor(150, 0, 0); // Rouge foncé
|
||||
$this->MultiCell(0, 5, utf8_decode($nonDeductibilityText), 0, 'C');
|
||||
$this->SetTextColor(0, 0, 0); // Revenir au noir
|
||||
|
||||
$this->Ln(10);
|
||||
|
||||
// Signature (Seule la ligne "Fait à [Ville], le [Date]" est conservée)
|
||||
$this->SetFont('Arial', 'I', 10);
|
||||
$this->Cell(0, 5, utf8_decode('Fait à BEAUTOR, le ') . date('d/m/Y'), 0, 1, 'R');
|
||||
// La ligne "Signature du Responsable" a été retirée.
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode publique pour générer et obtenir le PDF.
|
||||
* @param array $data Les données du don.
|
||||
* @param string $filename Le nom du fichier de sortie.
|
||||
* @param string $dest La destination (I: navigateur, D: téléchargement, F: fichier, S: string).
|
||||
*/
|
||||
public function generate(array $data, string $filename = 'Reçu_Don.pdf', string $dest = 'I')
|
||||
{
|
||||
$this->setDonationData($data);
|
||||
|
||||
// Initialisation du PDF
|
||||
$this->AliasNbPages();
|
||||
$this->AddPage();
|
||||
|
||||
// Génération du contenu
|
||||
$this->generateReceiptBody();
|
||||
|
||||
// Sortie du document
|
||||
// Le nom de fichier doit aussi être décodé pour l'en-tête HTTP si dest='D'
|
||||
return $this->Output($dest, utf8_decode($filename));
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,14 @@
|
||||
{# OPEN GRAPH / TWITTER CARD / SEO META #}
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url"
|
||||
content="{{ url(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) }}">
|
||||
content="{{ url(app.request.attributes.get('_route','app_home'), app.request.attributes.get('_route_params',[])) }}">
|
||||
<meta property="og:title" content="E-Cosplay | {{ block('title') }}">
|
||||
<meta property="og:description" content="{{ block('meta_description') }}">
|
||||
<meta property="og:image" content="{{ asset('assets/images/logo.jpg') | imagine_filter('webp') }}">
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:url"
|
||||
content="{{ url(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) }}">
|
||||
content="{{ url(app.request.attributes.get('_route','app_home'), app.request.attributes.get('_route_params',[])) }}">
|
||||
<meta name="twitter:title" content="E-Cosplay | {{ block('title') }}">
|
||||
<meta name="twitter:description" content="{{ block('meta_description') }}">
|
||||
<meta name="twitter:image" content="{{ asset('assets/images/logo.jpg') | imagine_filter('webp') }}">
|
||||
@@ -126,6 +126,7 @@
|
||||
{ 'name': 'Nos membres'|trans, 'route': 'app_members' },
|
||||
{ 'name': 'Nos événements'|trans, 'route': 'app_events' },
|
||||
{ 'name': 'Boutiques'|trans, 'route': 'app_shop' },
|
||||
{ 'name': 'Dons'|trans, 'route': 'app_dons' },
|
||||
{ 'name': 'Contact'|trans, 'route': 'app_contact' }
|
||||
] %}
|
||||
<header class="bg-white shadow-md sticky top-0 z-40">
|
||||
@@ -157,8 +158,8 @@
|
||||
|
||||
{# SÉLECTEUR DE LANGUE (Desktop) #}
|
||||
<div class="flex items-center space-x-2 border-l border-gray-200 pl-4">
|
||||
{% set current_route = app.request.attributes.get('_route') %}
|
||||
{% set current_params = app.request.attributes.get('_route_params') %}
|
||||
{% set current_route = app.request.attributes.get('_route','app_home') %}
|
||||
{% set current_params = app.request.attributes.get('_route_params',[]) %}
|
||||
|
||||
{# Fonction pour générer le chemin avec le paramètre 'lang' (doit être définie dans une extension Twig) #}
|
||||
{# En attendant, nous générons l'URL manuellement #}
|
||||
@@ -167,7 +168,6 @@
|
||||
{% for lang in ['fr', 'en'] %}
|
||||
{% set is_active_lang = (app.request.locale == lang) %}
|
||||
{% set lang_params = current_params|merge(current_query)|merge({'lang': lang}) %}
|
||||
|
||||
{# Générer l'URL en conservant les paramètres existants + le paramètre 'lang' #}
|
||||
{% set lang_url = path(current_route, lang_params) %}
|
||||
|
||||
@@ -282,8 +282,8 @@
|
||||
|
||||
{# SÉLECTEUR DE LANGUE (Mobile - Compact) #}
|
||||
<div class="flex items-center space-x-2">
|
||||
{% set current_route = app.request.attributes.get('_route') %}
|
||||
{% set current_params = app.request.attributes.get('_route_params') %}
|
||||
{% set current_route = app.request.attributes.get('_route','app_home') %}
|
||||
{% set current_params = app.request.attributes.get('_route_params',[]) %}
|
||||
{% set current_query = app.request.query.all %}
|
||||
|
||||
{% for lang in ['fr', 'en'] %}
|
||||
|
||||
177
templates/dons.twig
Normal file
177
templates/dons.twig
Normal file
@@ -0,0 +1,177 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{# --- METADATA & SCHEMA (Inchangé) --- #}
|
||||
{% block title %}{{'dons.title'|trans}}{% endblock %}
|
||||
{% block meta_description %}{{'dons.description'|trans}}{% endblock %}
|
||||
|
||||
{% block canonical_url %}<link rel="canonical" href="{{ url('app_dons') }}" />{% endblock %}
|
||||
{% block breadcrumb_schema %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "{{ 'breadcrumb.home'|trans }}",
|
||||
"item": "{{ app.request.schemeAndHttpHost }}"
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 2,
|
||||
"name": "{{ 'breadcrumb.dons'|trans }}",
|
||||
"item": "{{ app.request.schemeAndHttpHost }}{{ app.request.pathInfo }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{# --- BODY AVEC TAILWIND CSS --- #}
|
||||
{% block body %}
|
||||
<main class="py-12 md:py-20 bg-gray-50">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
||||
{# Section Header #}
|
||||
<header class="text-center mb-12">
|
||||
<h1 class="text-4xl sm:text-5xl font-extrabold text-indigo-700 tracking-tight">
|
||||
{{ 'dons.page_title'|trans }}
|
||||
</h1>
|
||||
<p class="mt-4 text-xl text-gray-600 max-w-2xl mx-auto">
|
||||
{{ 'dons.introduction'|trans|raw }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{# Impact Section Card #}
|
||||
<article class="bg-white shadow-xl rounded-lg p-6 md:p-8 mb-10 border-t-4 border-indigo-500">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-4">{{ 'dons.impact_title'|trans }}</h2>
|
||||
<p class="text-gray-600 mb-6">{{ 'dons.impact_text'|trans }}</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-indigo-600 mb-4">{{ 'dons.support_for_title'|trans }}</h3>
|
||||
|
||||
{# List of supported actions (Grid/Flex for better visual) #}
|
||||
<ul class="space-y-4">
|
||||
<li class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-6 w-6 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="ml-3 text-gray-700">
|
||||
<span class="font-medium text-gray-800">{{ 'dons.item.events_title'|trans }} :</span> {{ 'dons.item.events'|trans }}
|
||||
</p>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-6 w-6 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="ml-3 text-gray-700">
|
||||
<span class="font-medium text-gray-800">{{ 'dons.item.equipment_title'|trans }} :</span> {{ 'dons.item.equipment'|trans }}
|
||||
</p>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-6 w-6 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9h12" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="ml-3 text-gray-700">
|
||||
<span class="font-medium text-gray-800">{{ 'dons.item.website_hosting_title'|trans }} :</span> {{ 'dons.item.website_hosting'|trans }}
|
||||
</p>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-6 w-6 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v15m0 0l-3-3m3 3l3-3" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="ml-3 text-gray-700">
|
||||
<span class="font-medium text-gray-800">{{ 'dons.item.other_needs_title'|trans }} :</span> {{ 'dons.item.other_needs'|trans }}
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
{# Call to Action / Donation Form #}
|
||||
<div class="p-6 md:p-8 bg-indigo-50 rounded-lg shadow-xl">
|
||||
<header class="text-center mb-6">
|
||||
<h2 class="text-3xl font-bold text-gray-800 mb-2">{{ 'dons.make_a_donation_title'|trans }}</h2>
|
||||
<p class="text-gray-700">{{ 'dons.call_to_action_text'|trans }}</p>
|
||||
</header>
|
||||
|
||||
{# Début du formulaire #}
|
||||
<form data-turbo="false" action="{{ path('app_dons') }}" method="post" class="space-y-6">
|
||||
|
||||
{# Nom / Pseudo #}
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700">
|
||||
{{ 'form.name_label'|trans }} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="text" id="name" name="name" required
|
||||
placeholder="{{ 'form.name_placeholder'|trans }}"
|
||||
class="mt-1 block w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
|
||||
</div>
|
||||
|
||||
{# Email #}
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">
|
||||
{{ 'form.email_label'|trans }} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="email" id="email" name="email" required
|
||||
placeholder="{{ 'form.email_placeholder'|trans }}"
|
||||
class="mt-1 block w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
|
||||
</div>
|
||||
|
||||
{# Montant (Ammount) #}
|
||||
<div>
|
||||
<label for="amount" class="block text-sm font-medium text-gray-700">
|
||||
{{ 'form.amount_label'|trans }} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<div class="mt-1 relative rounded-md shadow-sm">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<span class="text-gray-500 sm:text-sm">€</span>
|
||||
</div>
|
||||
<input type="number" id="amount" name="amount" required min="1" step="0.01"
|
||||
placeholder="10.00"
|
||||
class="block w-full pl-8 pr-12 py-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
|
||||
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||
<span class="text-gray-500 sm:text-sm">EUR</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Message (Facultatif) #}
|
||||
<div>
|
||||
<label for="message" class="block text-sm font-medium text-gray-700">
|
||||
{{ 'form.message_label'|trans }} ({{ 'form.optional'|trans }})
|
||||
</label>
|
||||
<textarea id="message" name="message" rows="3"
|
||||
placeholder="{{ 'form.message_placeholder'|trans }}"
|
||||
class="mt-1 block w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"></textarea>
|
||||
</div>
|
||||
|
||||
{# Bouton de soumission #}
|
||||
<div class="text-center pt-4">
|
||||
<button type="submit"
|
||||
class="inline-flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-full shadow-lg text-white bg-indigo-600 hover:bg-indigo-700 transition duration-300 ease-in-out transform hover:scale-105 w-full sm:w-auto">
|
||||
<svg class="w-6 h-6 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
|
||||
</svg>
|
||||
{{ 'form.submit_button_dons'|trans }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{# Fin du formulaire #}
|
||||
|
||||
<p class="mt-6 text-center text-sm text-gray-500">
|
||||
{{ 'dons.thanks_note'|trans }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
81
templates/dons_success.twig
Normal file
81
templates/dons_success.twig
Normal file
@@ -0,0 +1,81 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{# --- DÉFINITION DES VARIABLES (Assurez-vous que 'dons' est passé par votre contrôleur) --- #}
|
||||
{% set donor_email = dons.email is defined ? dons.email : 'non-fourni' %}
|
||||
{% set donation_amount = dons.amount is defined ? dons.amount : 0 %}
|
||||
|
||||
{# --- METADATA & SCHEMA --- #}
|
||||
{% block title %}{{'thank_you.title'|trans}}{% endblock %}
|
||||
{% block meta_description %}{{'thank_you.email_sent_info'|trans}}{% endblock %}
|
||||
|
||||
{% block canonical_url %}<link rel="canonical" href="{{ url('app_dons') }}" />{% endblock %}
|
||||
{# Pas de breadcrumb nécessaire pour une page de confirmation de transaction #}
|
||||
|
||||
{# --- BODY AVEC TAILWIND CSS --- #}
|
||||
{% block body %}
|
||||
<main class="py-12 md:py-20 bg-gray-50 min-h-screen">
|
||||
<div class="max-w-xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow-2xl rounded-lg p-8 md:p-10 text-center border-t-8 border-green-500 transform hover:shadow-3xl transition duration-500">
|
||||
|
||||
{# Icône de Succès #}
|
||||
<div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-6">
|
||||
<svg class="h-8 w-8 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-800 mb-4">
|
||||
{{ 'thank_you.title'|trans }}
|
||||
</h1>
|
||||
|
||||
<p class="text-xl text-gray-700 mb-6">
|
||||
{{ 'thank_you.message_main'|trans|raw }}
|
||||
</p>
|
||||
|
||||
{# MONTANT DU DON #}
|
||||
{% if donation_amount > 0 %}
|
||||
<div class="bg-green-50 p-4 rounded-lg mb-6 border border-green-200">
|
||||
<p class="text-lg font-semibold text-green-700">
|
||||
{{ 'thank_you.amount_received'|trans }}
|
||||
</p>
|
||||
<p class="text-4xl font-extrabold text-green-600 mt-1">
|
||||
{# Utilisation du filtre pour le formatage monétaire #}
|
||||
{{ donation_amount|format_currency('EUR', locale='fr') }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<hr class="my-6 border-gray-200">
|
||||
|
||||
{# Informations sur l'email de confirmation (avec précision sur le paiement) #}
|
||||
<div class="p-4 bg-indigo-50 rounded-lg text-left">
|
||||
<h2 class="text-lg font-semibold text-indigo-700 mb-2">
|
||||
{{ 'thank_you.email_sent_title'|trans }}
|
||||
</h2>
|
||||
{# Cette clé de traduction contient le message de clarification #}
|
||||
<p class="text-gray-600 text-sm">
|
||||
{{ 'thank_you.email_sent_info'|trans }}
|
||||
</p>
|
||||
|
||||
{# Afficher l'email du donateur #}
|
||||
<p class="mt-2 text-indigo-600 font-medium text-sm truncate">
|
||||
{{ 'thank_you.email_recipient'|trans }} :
|
||||
<span class="font-bold">{{ donor_email }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{# Invitation à explorer #}
|
||||
<div class="mt-8">
|
||||
<a href="{{ url('app_home') }}"
|
||||
class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-lg text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-150 transform hover:scale-105">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
{{ 'thank_you.back_home_button'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
25
templates/error/404.twig
Normal file
25
templates/error/404.twig
Normal file
@@ -0,0 +1,25 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{# --- METADATA & SCHEMA --- #}
|
||||
{% block title %}Erreur 500 - {{ 'error.generic_title'|trans({}, 'messages') }}{% endblock %}
|
||||
{% block meta_description %}{{ 'error.generic_description'|trans({}, 'messages') }}{% endblock %}
|
||||
|
||||
{# --- BODY --- #}
|
||||
{% block body %}
|
||||
<div class="container mx-auto px-4 py-20 text-center">
|
||||
<h1 class="text-9xl font-extrabold text-red-700 mb-4">500</h1>
|
||||
|
||||
<h2 class="text-3xl font-bold text-gray-800 mb-6">
|
||||
{{ 'error.generic_title'|trans({}, 'messages') }}
|
||||
</h2>
|
||||
|
||||
<p class="text-lg text-gray-600 mb-8">
|
||||
{{ 'error.generic_description'|trans({}, 'messages') }}
|
||||
</p>
|
||||
|
||||
{# Utiliser path('home') ou path('app_home') selon la convention de votre application #}
|
||||
<a href="{{ path('app_home') | default('/') }}" class="inline-block px-6 py-3 text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition duration-150 shadow-md">
|
||||
Retourner à l'accueil
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
25
templates/error/error.twig
Normal file
25
templates/error/error.twig
Normal file
@@ -0,0 +1,25 @@
|
||||
{% extends 'base.twig' %}
|
||||
|
||||
{# --- METADATA & SCHEMA --- #}
|
||||
{% block title %}Erreur 500 - {{ 'error.generic_title'|trans({}, 'messages') }}{% endblock %}
|
||||
{% block meta_description %}{{ 'error.generic_description'|trans({}, 'messages') }}{% endblock %}
|
||||
|
||||
{# --- BODY --- #}
|
||||
{% block body %}
|
||||
<div class="container mx-auto px-4 py-20 text-center">
|
||||
<h1 class="text-9xl font-extrabold text-red-700 mb-4">500</h1>
|
||||
|
||||
<h2 class="text-3xl font-bold text-gray-800 mb-6">
|
||||
{{ 'error.generic_title'|trans({}, 'messages') }}
|
||||
</h2>
|
||||
|
||||
<p class="text-lg text-gray-600 mb-8">
|
||||
{{ 'error.generic_description'|trans({}, 'messages') }}
|
||||
</p>
|
||||
|
||||
{# L'utilisation de path('home') ou path('app_home') dépend de votre configuration de routes #}
|
||||
<a href="{{ path('app_home') | default('/') }}" class="inline-block px-6 py-3 text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition duration-150 shadow-md">
|
||||
Retourner à l'accueil
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
96
templates/mails/dons.twig
Normal file
96
templates/mails/dons.twig
Normal file
@@ -0,0 +1,96 @@
|
||||
{% extends 'mails/base.twig' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{# --- Section Header (Logo/Titre) --- #}
|
||||
<mj-section background-color="#4f46e5" padding-bottom="0">
|
||||
<mj-column>
|
||||
<mj-text align="center" font-size="24px" color="#ffffff" font-family="Helvetica Neue, Arial">
|
||||
Confirmation de votre Don
|
||||
</mj-text>
|
||||
<mj-text align="center" font-size="16px" color="#a5a2fa" font-family="Helvetica Neue, Arial" padding-top="0" padding-bottom="20px">
|
||||
Un immense merci pour votre générosité !
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
{# --- Section Confirmation & Montant --- #}
|
||||
<mj-section background-color="#ffffff">
|
||||
<mj-column>
|
||||
|
||||
{# Montant du Don (Mis en évidence) #}
|
||||
<mj-text font-size="16px" color="#555555" font-family="Helvetica Neue, Arial" align="center">
|
||||
Nous avons bien reçu votre soutien d'un montant de :
|
||||
</mj-text>
|
||||
<mj-text font-size="36px" font-weight="bold" color="#10b981" font-family="Helvetica Neue, Arial" align="center" padding-bottom="20px">
|
||||
{{ datas.don.amount|format_currency('EUR', locale='fr') }}
|
||||
</mj-text>
|
||||
|
||||
<mj-divider border-color="#e0e0e0" border-width="1px" padding="0 20px"></mj-divider>
|
||||
|
||||
{# Message de remerciement standard #}
|
||||
<mj-text font-size="16px" color="#555555" font-family="Helvetica Neue, Arial" padding-top="20px">
|
||||
Bonjour {% if datas.don.name %}{{ datas.don.name }}{% else %}Cher Donateur{% endif %},
|
||||
</mj-text>
|
||||
<mj-text font-size="16px" color="#555555" font-family="Helvetica Neue, Arial">
|
||||
Votre don a été confirmé avec succès. Votre contribution est essentielle pour l'organisation de nos événements, l'achat de matériel et le maintien de nos activités.
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
{# --- Section Détails du Don --- #}
|
||||
<mj-section background-color="#ffffff" padding-top="0">
|
||||
<mj-column border="1px solid #e0eeef" padding="10px" border-radius="4px">
|
||||
<mj-text font-size="18px" font-weight="bold" color="#4f46e5" font-family="Helvetica Neue, Arial" padding-bottom="10px">
|
||||
Détails de votre transaction
|
||||
</mj-text>
|
||||
|
||||
{# Nom / Pseudo #}
|
||||
<mj-table font-size="14px" color="#555555" font-family="Helvetica Neue, Arial" padding-bottom="5px">
|
||||
<tr>
|
||||
<td style="width: 150px; padding-bottom: 5px;">Nom/Pseudo :</td>
|
||||
<td style="font-weight: bold; padding-bottom: 5px;">{% if datas.don.name %}{{ datas.don.name }}{% else %}Anonyme{% endif %}</td>
|
||||
</tr>
|
||||
</mj-table>
|
||||
|
||||
{# Montant #}
|
||||
<mj-table font-size="14px" color="#555555" font-family="Helvetica Neue, Arial" padding-bottom="5px">
|
||||
<tr>
|
||||
<td style="width: 150px; padding-bottom: 5px;">Montant :</td>
|
||||
<td style="font-weight: bold; color: #10b981; padding-bottom: 5px;">{{ datas.don.amount|format_currency('EUR', locale='fr') }}</td>
|
||||
</tr>
|
||||
</mj-table>
|
||||
|
||||
{# Message facultatif #}
|
||||
{% if datas.don.message %}
|
||||
<mj-table font-size="14px" color="#555555" font-family="Helvetica Neue, Arial" padding-top="10px">
|
||||
<tr>
|
||||
<td style="width: 150px; vertical-align: top;">Message :</td>
|
||||
<td><em style="color: #777;">"{{ datas.don.message }}"</em></td>
|
||||
</tr>
|
||||
</mj-table>
|
||||
{% endif %}
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
{# --- Section Reçu Fiscal & Non-déductibilité (MIS À JOUR) --- #}
|
||||
<mj-section background-color="#f4f4f4">
|
||||
<mj-column>
|
||||
|
||||
{# Message concernant le Reçu Fiscal en Pièce Jointe #}
|
||||
<mj-text font-size="15px" color="#333333" font-family="Helvetica Neue, Arial" align="center" padding-bottom="5px">
|
||||
<span style="font-size: 20px;">📎</span>
|
||||
<span style="font-weight: bold; color: #4f46e5;">Votre reçu se trouve en pièce jointe de cet e-mail.</span>
|
||||
</mj-text>
|
||||
|
||||
{# Message d'avertissement sur la réduction d'impôt #}
|
||||
<mj-text font-size="14px" color="#993300" font-family="Helvetica Neue, Arial" align="center" font-style="italic" padding-bottom="15px">
|
||||
<strong>NOTE IMPORTANTE :</strong> Notez que notre association ne vous permet pas de bénéficier d'une réduction d'impôt.
|
||||
</mj-text>
|
||||
|
||||
<mj-text font-size="12px" color="#999999" font-family="Helvetica Neue, Arial" align="center">
|
||||
Si vous avez des questions, n'hésitez pas à nous contacter.
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
{% endblock %}
|
||||
50
templates/mails/dons_new.twig
Normal file
50
templates/mails/dons_new.twig
Normal file
@@ -0,0 +1,50 @@
|
||||
{% extends 'mails/base.twig' %}
|
||||
|
||||
{% block content %}
|
||||
{# --- Section Header (Alerte Nouveau Don) --- #}
|
||||
<mj-section background-color="#ff9900" padding-bottom="20px">
|
||||
<mj-column>
|
||||
<mj-text align="center" font-size="28px" color="#ffffff" font-family="Helvetica Neue, Arial" font-weight="bold">
|
||||
🚨 NOUVEAU DON ARRIVÉ ! 🚨
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
{# --- Section Détails Clés --- #}
|
||||
<mj-section background-color="#ffffff" padding-top="20px">
|
||||
<mj-column>
|
||||
<mj-text font-size="16px" color="#333333" font-family="Helvetica Neue, Arial" padding-bottom="10px">
|
||||
Un nouvel acte de générosité a été enregistré. Voici les détails :
|
||||
</mj-text>
|
||||
|
||||
{# Montant du Don (Mis en évidence) #}
|
||||
<mj-text font-size="20px" font-weight="bold" color="#ff9900" font-family="Helvetica Neue, Arial" align="center">
|
||||
Montant : {{ datas.don.amount|format_currency('EUR', locale='fr') }}
|
||||
</mj-text>
|
||||
|
||||
<mj-divider border-color="#e0e0e0" border-width="1px" padding="10px 20px"></mj-divider>
|
||||
|
||||
{# Informations du Donateur #}
|
||||
<mj-table font-size="15px" color="#555555" font-family="Helvetica Neue, Arial" padding-top="10px">
|
||||
<tr>
|
||||
<td style="width: 150px; padding-bottom: 5px; font-weight: bold;">Donateur :</td>
|
||||
<td style="padding-bottom: 5px;">{% if datas.don.name %}{{ datas.don.name }}{% else %}Anonyme / Non spécifié{% endif %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 150px; padding-bottom: 5px; font-weight: bold;">E-mail :</td>
|
||||
<td style="padding-bottom: 5px; color: #4f46e5;">{{ datas.don.email }}</td>
|
||||
</tr>
|
||||
</mj-table>
|
||||
|
||||
{# Message facultatif #}
|
||||
{% if datas.don.message %}
|
||||
<mj-text font-size="16px" font-weight="bold" color="#333333" font-family="Helvetica Neue, Arial" padding-top="20px">
|
||||
Message laissé par le donateur :
|
||||
</mj-text>
|
||||
<mj-text font-size="15px" color="#555555" font-family="Helvetica Neue, Arial" border="1px solid #e0e0e0" padding="10px" background-color="#fafafa" border-radius="4px">
|
||||
"{{ datas.don.message }}"
|
||||
</mj-text>
|
||||
{% endif %}
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
{% endblock %}
|
||||
@@ -568,3 +568,52 @@ logged_in_as: Signed in as
|
||||
logout_link: Log Out
|
||||
page.login: Login
|
||||
logged_admin: Administration
|
||||
# messages.en.yaml
|
||||
|
||||
# --- PAGE DE DON (dons.html.twig) ---
|
||||
dons.title: Support Us - Make a Donation to the Association
|
||||
dons.description: Your donation helps support our association, organize events, purchase equipment, and fund the website.
|
||||
dons.page_title: Make a Donation and Support Our Association
|
||||
dons.introduction: Your generosity is essential for the life and development of our association. Every donation, large or small, allows us to realize our projects and fulfill our mission.
|
||||
dons.impact_title: The Impact of Your Donation
|
||||
dons.impact_text: By making a donation, you contribute directly to all our activities. Your support concretely helps us to
|
||||
dons.support_for_title: What do your contributions fund?
|
||||
dons.item.events_title: Event Organization
|
||||
dons.item.events: Organizing events, workshops, and gatherings for the community.
|
||||
dons.item.equipment_title: Equipment Purchase
|
||||
dons.item.equipment: Purchasing and maintaining the necessary equipment for our activities (tools, specific gear, etc.).
|
||||
dons.item.website_hosting_title: Website Funding
|
||||
dons.item.website_hosting: Funding the hosting and maintenance of this website, ensuring our online presence.
|
||||
dons.item.other_needs_title: Operating Costs
|
||||
dons.item.other_needs: Covering operating expenses and unforeseen needs of the association.
|
||||
|
||||
dons.make_a_donation_title: Take Action
|
||||
dons.call_to_action_text: Click the button below to make your donation securely through our partner platform.
|
||||
dons.thanks_note: A big thank you for your valuable support.
|
||||
|
||||
|
||||
# --- FORMULAIRE DE DON (Clés utilisées dans le bloc form) ---
|
||||
form.name_label: Your Name or Nickname
|
||||
form.name_placeholder: Ex. John Doe or A Friend
|
||||
form.email_label: Your Email Address
|
||||
form.email_placeholder: youraddress@example.com
|
||||
form.amount_label: Donation Amount
|
||||
form.message_label: Message of Encouragement
|
||||
form.message_placeholder: Leave a short note for the team (optional)
|
||||
form.optional: optional
|
||||
form.submit_button_dons: Donate and Continue to Payment
|
||||
|
||||
|
||||
# --- PAGE DE CONFIRMATION DE DON (dons/confirmation.html.twig) ---
|
||||
thank_you.title: A Huge Thank You for Your Donation!
|
||||
thank_you.message_main: "Your generosity is invaluable and will allow us to continue our mission: <strong>organizing events, purchasing equipment, and sustaining our association.</strong>"
|
||||
thank_you.mount_received: Amount of Your Support Received # Note: Corrected 'mount' to 'amount' in translation
|
||||
thank_you.email_sent_title: Your Confirmation is on its Way
|
||||
thank_you.email_sent_info: Once the payment is confirmed by our partner, you will receive an email containing all the details of your donation and your tax receipt. This process usually takes a few minutes.
|
||||
thank_you.email_recipient: Email sent to
|
||||
thank_you.back_home_button: Return to Home
|
||||
thank_you.amount_received: Amount of Your Support Received
|
||||
error.not_found_title: Page Not Found
|
||||
error.not_found_description: We are sorry, but the page you are looking for does not exist or has been moved. Please check the address or return to the homepage.
|
||||
error.generic_title: Oops, an error occurred
|
||||
error.sgeneric_description: We encountered an unexpected problem on the server. Please try again later. If the error persists, contact technical support.
|
||||
|
||||
@@ -510,3 +510,49 @@ logged_in_as: Connecté en tant que
|
||||
logout_link: Déconnexion
|
||||
page.login: Connexion
|
||||
logged_admin: Administration
|
||||
|
||||
dons.impact_title: L'impact de votre Don
|
||||
dons.impact_text: En faisant un don, vous contribuez directement à l'ensemble de nos activités. Votre soutien nous aide concrètement à
|
||||
|
||||
dons.title: Soutenez-nous - Faites un Don à l'Association
|
||||
dons.description: Votre don permet de soutenir notre association, d'organiser des événements, d'acheter du matériel et de financer le site internet.
|
||||
dons.page_title: Faites un Don et Soutenez Notre Association
|
||||
dons.introduction: Votre générosité est essentielle pour la vie et le développement de notre association. Chaque don, petit ou grand, nous permet de concrétiser nos projets et d'assurer notre mission.
|
||||
dons.support_for_title: À quoi servent vos contributions ?
|
||||
|
||||
dons.item.events: Organiser des événements, des ateliers et des rencontres pour la communauté.
|
||||
dons.item.equipment: Acheter et entretenir le matériel nécessaire à nos activités (outils, équipements spécifiques, etc.).
|
||||
dons.item.website_hosting: Financer l'hébergement et la maintenance de ce site internet, garantissant notre présence en ligne.
|
||||
dons.item.other_needs: Couvrir les frais de fonctionnement et les besoins imprévus de l'association.
|
||||
|
||||
dons.item.events_title: Organisation d'événements
|
||||
dons.item.equipment_title: Achat de matériel
|
||||
dons.item.website_hosting_title: Financement du site web
|
||||
dons.item.other_needs_title: Frais de fonctionnement
|
||||
form.name_label: Votre nom ou pseudo
|
||||
form.name_placeholder: Ex. Jean Dupont ou Un Ami
|
||||
form.email_label: Votre adresse e-mail
|
||||
form.email_placeholder: votreadresse@exemple.com
|
||||
form.amount_label: Montant du don
|
||||
form.message_label: Message d'encouragement
|
||||
form.message_placeholder: Laissez un petit mot pour l'équipe (facultatif)
|
||||
form.optional: facultatif
|
||||
form.submit_button_dons: Faire un don et continuer vers le paiement
|
||||
dons.thanks_note: Un grand merci pour votre précieux soutien.
|
||||
dons.make_a_donation_title: Passer à l'action
|
||||
dons.call_to_action_text: Cliquez sur le bouton ci-dessous pour effectuer votre don en toute sécurité via notre plateforme partenaire.
|
||||
|
||||
thank_you.title: Un immense Merci pour votre Don !
|
||||
thank_you.message_main: "Votre générosité est précieuse et nous permettra de continuer notre mission : <strong>organiser des événements, acheter du matériel et faire vivre notre association.</strong>"
|
||||
thank_you.mount_received: Montant de votre soutien reçu
|
||||
thank_you.email_sent_title: Votre confirmation est en route
|
||||
thank_you.email_sent_info: Une fois le paiement confirmé par notre partenaire, vous recevrez un e-mail contenant tous les détails de votre don et votre reçu fiscal. Ce processus prend généralement quelques minutes.
|
||||
|
||||
thank_you. email_recipient: E-mail envoyé à
|
||||
thank_you.back_home_button: Retourner à l'accueil
|
||||
thank_you.amount_received: Montant de votre soutien reçu
|
||||
|
||||
error.not_found_title: Page introuvable
|
||||
error.not_found_description: Nous sommes désolés, mais la page que vous recherchez n'existe pas ou a été déplacée. Veuillez vérifier l'adresse ou retourner à la page d'accueil.
|
||||
error.generic_title: Oups, une erreur s'est produite
|
||||
error.generic_description: Nous avons rencontré un problème inattendu sur le serveur. Veuillez réessayer ultérieurement. Si l'erreur persiste, contactez le support technique.
|
||||
|
||||
Reference in New Issue
Block a user