add success email #14
BIN
assets/images/email/background.jpeg
Normal file
BIN
assets/images/email/background.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
BIN
assets/images/email/header.jpeg
Normal file
BIN
assets/images/email/header.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
@ -7,6 +7,7 @@
|
||||
"php": ">=8.2",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"chillerlan/php-qrcode": "^5.0",
|
||||
"doctrine/annotations": "^2.0",
|
||||
"doctrine/dbal": "^3",
|
||||
"doctrine/doctrine-bundle": "^2.13",
|
||||
|
161
composer.lock
generated
161
composer.lock
generated
@ -4,8 +4,167 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6e7803d436b49242a4a3816fdc776d3a",
|
||||
"content-hash": "26b10019eec6bd118285cb25e8daf878",
|
||||
"packages": [
|
||||
{
|
||||
"name": "chillerlan/php-qrcode",
|
||||
"version": "5.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/chillerlan/php-qrcode.git",
|
||||
"reference": "42e215640e9ebdd857570c9e4e52245d1ee51de2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/42e215640e9ebdd857570c9e4e52245d1ee51de2",
|
||||
"reference": "42e215640e9ebdd857570c9e4e52245d1ee51de2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"chillerlan/php-settings-container": "^2.1.6 || ^3.2.1",
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"chillerlan/php-authenticator": "^4.3.1 || ^5.2.1",
|
||||
"ext-fileinfo": "*",
|
||||
"phan/phan": "^5.4.5",
|
||||
"phpcompatibility/php-compatibility": "10.x-dev",
|
||||
"phpmd/phpmd": "^2.15",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"setasign/fpdf": "^1.8.2",
|
||||
"slevomat/coding-standard": "^8.15",
|
||||
"squizlabs/php_codesniffer": "^3.11"
|
||||
},
|
||||
"suggest": {
|
||||
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
|
||||
"setasign/fpdf": "Required to use the QR FPDF output.",
|
||||
"simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"chillerlan\\QRCode\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT",
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kazuhiko Arase",
|
||||
"homepage": "https://github.com/kazuhikoarase/qrcode-generator"
|
||||
},
|
||||
{
|
||||
"name": "ZXing Authors",
|
||||
"homepage": "https://github.com/zxing/zxing"
|
||||
},
|
||||
{
|
||||
"name": "Ashot Khanamiryan",
|
||||
"homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder"
|
||||
},
|
||||
{
|
||||
"name": "Smiley",
|
||||
"email": "smiley@chillerlan.net",
|
||||
"homepage": "https://github.com/codemasher"
|
||||
},
|
||||
{
|
||||
"name": "Contributors",
|
||||
"homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A QR Code generator and reader with a user-friendly API. PHP 7.4+",
|
||||
"homepage": "https://github.com/chillerlan/php-qrcode",
|
||||
"keywords": [
|
||||
"phpqrcode",
|
||||
"qr",
|
||||
"qr code",
|
||||
"qr-reader",
|
||||
"qrcode",
|
||||
"qrcode-generator",
|
||||
"qrcode-reader"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://php-qrcode.readthedocs.io",
|
||||
"issues": "https://github.com/chillerlan/php-qrcode/issues",
|
||||
"source": "https://github.com/chillerlan/php-qrcode"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://ko-fi.com/codemasher",
|
||||
"type": "Ko-Fi"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-21T16:12:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "chillerlan/php-settings-container",
|
||||
"version": "3.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/chillerlan/php-settings-container.git",
|
||||
"reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/95ed3e9676a1d47cab2e3174d19b43f5dbf52681",
|
||||
"reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpmd/phpmd": "^2.15",
|
||||
"phpstan/phpstan": "^1.11",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.2",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"squizlabs/php_codesniffer": "^3.10"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"chillerlan\\Settings\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Smiley",
|
||||
"email": "smiley@chillerlan.net",
|
||||
"homepage": "https://github.com/codemasher"
|
||||
}
|
||||
],
|
||||
"description": "A container class for immutable settings objects. Not a DI container.",
|
||||
"homepage": "https://github.com/chillerlan/php-settings-container",
|
||||
"keywords": [
|
||||
"Settings",
|
||||
"configuration",
|
||||
"container",
|
||||
"helper"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/chillerlan/php-settings-container/issues",
|
||||
"source": "https://github.com/chillerlan/php-settings-container"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://ko-fi.com/codemasher",
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-16T11:13:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "3.4.3",
|
||||
|
@ -1,5 +1,7 @@
|
||||
twig:
|
||||
file_name_pattern: '*.twig'
|
||||
paths:
|
||||
'%kernel.project_dir%/assets/images/email': images
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
|
20
src/Controller/SuccessController.php
Normal file
20
src/Controller/SuccessController.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Service\TicketService;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class SuccessController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/success', name: 'app_success_page', methods: Request::METHOD_GET)]
|
||||
public function __invoke(TicketService $service, Request $request): Response
|
||||
{
|
||||
$service->completePayment((string)$request->query->get('session_id'));
|
||||
|
||||
return $this->render('ticket/success.html.twig');
|
||||
}
|
||||
}
|
24
src/Controller/TestController.php
Normal file
24
src/Controller/TestController.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use Nucleos\DompdfBundle\Factory\DompdfFactory;
|
||||
use Nucleos\DompdfBundle\Wrapper\DompdfWrapper;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class TestController extends AbstractController
|
||||
{
|
||||
#[Route('/test')]
|
||||
public function index(Filesystem $filesystem): Response
|
||||
{
|
||||
$filesystem->dumpFile('test.pdf', $content);
|
||||
|
||||
return $this->render('test.html.twig', ['qr' => (new QRCode())->render('https://www.google.com')]);
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ final class TicketController extends AbstractController
|
||||
return $this->json(['id' => $this->service->handleTicketData($ticketData)->id]);
|
||||
}
|
||||
|
||||
#[Route(path: '/success', name: 'app_success', methods: Request::METHOD_GET)]
|
||||
#[Route(path: '/success', name: 'app_order_success', methods: Request::METHOD_GET)]
|
||||
public function success(Request $request): Response
|
||||
{
|
||||
$sessionId = $request->query->get('session_id');
|
||||
@ -50,7 +50,7 @@ final class TicketController extends AbstractController
|
||||
return $this->redirectToRoute('app_ticket');
|
||||
}
|
||||
|
||||
return $this->render('ticket/success.html.twig');
|
||||
return $this->redirectToRoute('app_success_page');
|
||||
}
|
||||
|
||||
#[Route(path: '/cancelled', name: 'app_cancelled', methods: Request::METHOD_GET)]
|
||||
|
64
src/Service/TicketEmailService.php
Normal file
64
src/Service/TicketEmailService.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Controller\Admin\TicketCrudController;
|
||||
use App\Entity\Payment;
|
||||
use App\Entity\Ticket;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use Nucleos\DompdfBundle\Factory\DompdfFactory;
|
||||
use Nucleos\DompdfBundle\Wrapper\DompdfWrapper;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
class TicketEmailService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly MailerInterface $mailer,
|
||||
#[Autowire(env: 'SENDER_MAIL')]
|
||||
private readonly string $senderMail,
|
||||
private readonly UrlGeneratorInterface $urlGenerator,
|
||||
private readonly Environment $twig,
|
||||
) {
|
||||
}
|
||||
|
||||
public function sendSuccessEmail(Payment $payment): void
|
||||
{
|
||||
$mail = (new TemplatedEmail())
|
||||
->htmlTemplate('email/order.html.twig')
|
||||
->subject('Abiball Ticket')
|
||||
->from(new Address($this->senderMail, 'Noreply'))
|
||||
->to(new Address($payment->getCustomer()?->getEmail(), $payment->getCustomer()?->getFirstname() . ' ' . $payment->getCustomer()?->getLastname()))
|
||||
->context([
|
||||
'payment' => $payment,
|
||||
]);
|
||||
|
||||
$i = 0;
|
||||
foreach ($payment->getCustomer()?->getTickets() as $ticket) {
|
||||
++$i;
|
||||
$mail->addPart(new DataPart($this->generateTicket($ticket), "ticket-$i.pdf", 'application/pdf'));
|
||||
}
|
||||
|
||||
$this->mailer->send($mail);
|
||||
}
|
||||
|
||||
private function generateTicket(Ticket $ticket): string
|
||||
{
|
||||
return (new DompdfWrapper(new DompdfFactory()))->getPdf($this->twig->render('test.html.twig', ['qr' => (new QRCode())->render($this->generateUrl($ticket))]));
|
||||
}
|
||||
|
||||
private function generateUrl(Ticket $ticket): string
|
||||
{
|
||||
return $this->urlGenerator->generate('admin', [
|
||||
'crudAction' => 'detail',
|
||||
'crudControllerFqcn' => TicketCrudController::class,
|
||||
'entityId' => $ticket->getCustomer()?->getId()
|
||||
], UrlGeneratorInterface::ABSOLUTE_URL);
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ class TicketService
|
||||
private readonly UrlGeneratorInterface $generator,
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly PaymentRepository $paymentRepository,
|
||||
private readonly TicketEmailService $emailService,
|
||||
#[Autowire(env: 'STRIPE_SECRET_KEY')]
|
||||
string $stripeKey
|
||||
) {
|
||||
@ -36,18 +37,6 @@ class TicketService
|
||||
return $session;
|
||||
}
|
||||
|
||||
public function saveTicketData(TicketFormData $data, string $sessionId): void
|
||||
{
|
||||
$payment = (new Payment())
|
||||
->setSessionId($sessionId)
|
||||
->setCompleted(false)
|
||||
->setCustomer($this->createEntityFromData($data));
|
||||
|
||||
$this->em->persist($payment);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
|
||||
public function completePayment(string $sessionId): bool
|
||||
{
|
||||
if (!$payment = $this->paymentRepository->findOneBy(['sessionId' => $sessionId])) {
|
||||
@ -57,9 +46,23 @@ class TicketService
|
||||
$payment->setCompleted(true);
|
||||
$this->em->flush();
|
||||
|
||||
$this->emailService->sendSuccessEmail($payment);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private function saveTicketData(TicketFormData $data, string $sessionId): void
|
||||
{
|
||||
$payment = (new Payment())
|
||||
->setSessionId($sessionId)
|
||||
->setCompleted(false)
|
||||
->setCustomer($this->createEntityFromData($data));
|
||||
|
||||
$this->em->persist($payment);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
private function getLineItems(array $tickets): array
|
||||
{
|
||||
$lineItems = [];
|
||||
@ -87,7 +90,7 @@ class TicketService
|
||||
'line_items' => $lineItems,
|
||||
'mode' => 'payment',
|
||||
'customer_email' => $email,
|
||||
'success_url' => $this->generator->generate('app_success', [], 0) . '?session_id={CHECKOUT_SESSION_ID}',
|
||||
'success_url' => $this->generator->generate('app_order_success', [], 0) . '?session_id={CHECKOUT_SESSION_ID}',
|
||||
'cancel_url' => $this->generator->generate('app_cancelled', [], 0),
|
||||
]);
|
||||
}
|
||||
|
87
templates/email/order.html.twig
Normal file
87
templates/email/order.html.twig
Normal file
@ -0,0 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Abiball - Freie Waldorfschule Bremen</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
.email-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
@media screen and (max-width: 600px) {
|
||||
.email-container {
|
||||
width: 100% !important;
|
||||
padding: 10px !important;
|
||||
}
|
||||
.content-block {
|
||||
padding: 15px !important;
|
||||
}
|
||||
.header-image {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
h1 {
|
||||
font-size: 24px !important;
|
||||
}
|
||||
h2 {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
p {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
.details-box {
|
||||
padding: 15px !important;
|
||||
margin: 15px 0 !important;
|
||||
}
|
||||
.cta-button {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 10px 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background-color: #f6f6f6; -webkit-font-smoothing: antialiased; font-size: 16px; line-height: 1.4; color: #333333;">
|
||||
<div style="width: 100%; background-color: #f6f6f6; padding: 15px 0;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" class="email-container" align="center">
|
||||
<tr>
|
||||
<td style="background-color: #372064; padding: 15px; text-align: center; border-radius: 8px 8px 0 0;">
|
||||
<img src="{{ email.image('@images/header.jpeg') }}" alt="Abiball" class="header-image" style="width: 100%; max-width: 500px; height: auto; border-radius: 4px;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="background-color: #ffffff; padding: 0;">
|
||||
<img src="{{ email.image('@images/background.jpeg') }}" alt="Feier" style="width: 100%; height: auto; display: block;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="background-color: #ffffff;" class="content-block">
|
||||
<div style="padding: 30px 20px; text-align: center;">
|
||||
<h1 style="color: #372064; font-size: 32px; margin: 0 0 20px 0; font-weight: 600;">Du bist eingeladen!</h1>
|
||||
<p style="color: #666666; font-size: 18px; line-height: 1.6; margin: 0 0 30px 0;">
|
||||
Sei dabei bei einem Abend voller Feierlichkeiten, Tanz und unvergesslicher Momente beim Abiball der Freien Waldorfschule Bremen
|
||||
</p>
|
||||
<div class="details-box" style="background-color: #f8f9fa; border-radius: 8px; padding: 25px 20px; margin: 0 0 25px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">
|
||||
<h2 style="color: #372064; font-size: 24px; margin: 0 0 20px 0; font-weight: 500;">Veranstaltungsdetails</h2>
|
||||
<p style="color: #666666; margin: 10px 0; font-size: 16px;">Datum: [Datum]</p>
|
||||
<p style="color: #666666; margin: 10px 0; font-size: 16px;">Uhrzeit: [Uhrzeit]</p>
|
||||
<p style="color: #666666; margin: 10px 0; font-size: 16px;">Adresse: [Adresse]</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
13
templates/test.html.twig
Normal file
13
templates/test.html.twig
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user