Files
e-ticket/templates/pdf/billet.html.twig
Serreau Jovann 7a29372b60 Save Stripe payment details on order confirmation, add arrive early tip
- Retrieve PaymentIntent on success redirect, save payment_method, card_brand, card_last4
- Display payment info on /ma-commande page (card type + last 4 digits)
- Add "Il est recommande d'arriver en avance" to practical info on ticket PDF
- Migration for payment_method, card_brand, card_last4 columns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:56:50 +01:00

431 lines
17 KiB
Twig

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>{{ ticket.reference }} - {{ event.title }}</title>
<style>
{% set ac = design ? design.accentColor : '#4f46e5' %}
{% set inv_color = design ? design.invitationColor : '#d4a017' %}
{% set inv_title = design ? design.invitationTitle : 'Invitation' %}
@page { size: A4; margin: 0; }
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 11px;
color: #111;
margin: 0;
padding: 0;
width: 210mm;
height: 297mm;
}
.main-table {
width: 100%;
border-collapse: collapse;
}
.main-table td {
vertical-align: top;
}
/* ====== HEADER ====== */
.header {
background: {{ ac }};
color: #fff;
padding: 18px 30px;
}
.header-table {
width: 100%;
border-collapse: collapse;
}
.header-table td {
vertical-align: middle;
}
.header-org {
font-size: 13px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 2px;
}
.header-badge {
font-size: 9px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 2px;
text-align: right;
opacity: 0.7;
}
/* ====== CONTENT ====== */
.content-table {
width: 100%;
border-collapse: collapse;
}
.content-left {
width: 62%;
padding: 24px 20px 24px 30px;
border-right: 3px solid {{ ac }};
}
.content-right {
width: 38%;
padding: 10px;
text-align: center;
background: #fafafa;
}
.content-right img {
max-width: 100%;
}
/* ====== EVENT INFO ====== */
.event-title {
font-size: 20px;
font-weight: bold;
text-transform: uppercase;
margin: 0 0 12px 0;
line-height: 1.2;
}
.info-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 6px;
}
.info-table td {
padding: 3px 0;
border-bottom: 1px solid #eee;
}
.info-table tr:last-child td {
border-bottom: none;
}
.lbl {
width: 60px;
font-size: 7px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #999;
}
.val {
font-size: 11px;
font-weight: bold;
}
/* ====== SEPARATOR ====== */
.sep {
height: 3px;
background: {{ ac }};
margin: 12px 0;
}
/* ====== BILLET INFO ====== */
.billet-name {
font-size: 16px;
font-weight: bold;
text-transform: uppercase;
margin: 0 0 2px 0;
}
.billet-price {
font-size: 20px;
font-weight: bold;
color: {{ ac }};
margin: 0 0 10px 0;
}
.meta-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 6px;
}
.meta-table td {
padding: 2px 6px 2px 0;
vertical-align: top;
}
.meta-lbl {
font-size: 7px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #999;
}
.meta-val {
font-size: 10px;
font-weight: bold;
}
/* ====== BADGES ====== */
.badge-exit-def {
display: inline-block;
padding: 3px 6px;
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
background: #fee2e2;
color: #991b1b;
border: 1px solid #991b1b;
}
.badge-exit-libre {
display: inline-block;
padding: 3px 6px;
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
background: #dcfce7;
color: #166534;
border: 1px solid #166534;
}
.badge-invitation {
display: inline-block;
padding: 3px 6px;
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
color: #fff;
letter-spacing: 1px;
}
/* ====== QR ====== */
.qr-section {
margin-top: 14px;
padding-top: 10px;
border-top: 1px solid #eee;
}
.qr-table {
width: 100%;
border-collapse: collapse;
}
.qr-table td {
vertical-align: bottom;
}
.qr-img {
width: 130px;
height: 130px;
}
.ref-lbl {
font-size: 7px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #bbb;
}
.ref-val {
font-size: 9px;
font-weight: bold;
font-family: 'Courier New', monospace;
color: #666;
}
/* ====== FOOTER ====== */
.footer {
background: {{ ac }};
color: #fff;
padding: 14px 30px;
}
.footer-table {
width: 100%;
border-collapse: collapse;
}
.footer-table td {
vertical-align: middle;
}
.footer-logo img {
width: 40px;
height: 40px;
}
.footer-org {
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
}
.footer-details {
font-size: 8px;
opacity: 0.7;
margin-top: 2px;
}
.footer-powered {
font-size: 6px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
opacity: 0.4;
text-align: right;
}
</style>
</head>
<body>
<!-- HEADER -->
<div class="header">
<table class="header-table">
<tr>
<td>
<span class="header-org">Billet Entree — {{ event.title }}{{ ticket.billetName }}</span>
</td>
</tr>
</table>
</div>
<!-- CONTENT -->
<table class="content-table">
<tr>
<td class="content-left">
<div class="event-title">{{ event.title }}</div>
<table class="info-table">
<tr><td class="lbl">Date</td><td class="val">{{ event.startAt|date('d/m/Y') }}</td></tr>
<tr><td class="lbl">Horaires</td><td class="val">{{ event.startAt|date('H:i') }}{{ event.endAt|date('H:i') }}</td></tr>
<tr><td class="lbl">Lieu</td><td class="val">{{ event.address }}</td></tr>
<tr><td class="lbl">Ville</td><td class="val">{{ event.zipcode }} {{ event.city }}</td></tr>
</table>
<div class="sep"></div>
<div class="billet-name">{{ ticket.billetName }}</div>
<div class="billet-price">{{ ticket.unitPriceHTDecimal|number_format(2, ',', ' ') }} &euro; HT</div>
<table class="meta-table">
<tr>
<td><div class="meta-lbl">Categorie</div><div class="meta-val">{{ ticket.billet.category.name }}</div></td>
<td><div class="meta-lbl">Date d'achat</div><div class="meta-val">{{ order.paidAt ? order.paidAt|date('d/m/Y H:i') : order.createdAt|date('d/m/Y H:i') }}</div></td>
</tr>
<tr>
<td><div class="meta-lbl">Acheteur</div><div class="meta-val">{{ order.firstName }} {{ order.lastName }}</div></td>
<td><div class="meta-lbl">E-mail</div><div class="meta-val">{{ order.email }}</div></td>
</tr>
</table>
<div style="margin-top: 8px;">
{% if ticket.billet.definedExit %}
<span class="badge-exit-def">Sortie definitive</span>
{% else %}
<span class="badge-exit-libre">Sortie - Entree illimitee</span>
{% endif %}
{% if ticket.invitation %}
<span class="badge-invitation" style="background: {{ inv_color }};">{{ inv_title }}</span>
{% endif %}
</div>
<div class="qr-section">
<table class="qr-table">
<tr>
<td style="width: 140px;">
<img src="{{ qrBase64 }}" alt="QR" class="qr-img">
<div style="font-size: 6px; font-weight: bold; text-align: center; color: #999; margin-top: 4px; text-transform: uppercase;">Presentez ce QR code<br>pour valider votre ticket</div>
</td>
<td style="text-align: right;">
<div class="ref-lbl">Reference billet</div>
<div class="ref-val">{{ ticket.reference }}</div>
<br>
<div class="ref-lbl">Commande</div>
<div class="ref-val">{{ order.orderNumber }}</div>
<br>
<div class="ref-lbl">Cle de securite</div>
<div class="ref-val" style="letter-spacing: 2px;">{{ ticket.securityKey }}</div>
</td>
</tr>
</table>
</div>
</td>
<td class="content-right">
{% if posterBase64 %}
<img src="{{ posterBase64 }}" alt="{{ event.title }}">
{% endif %}
</td>
</tr>
</table>
<!-- BLOC ORGA + DESCRIPTION -->
<table style="width: 100%; border-collapse: collapse; border-top: 3px solid {{ ac }};">
<tr>
<td style="width: 50%; padding: 14px 15px 14px 30px; vertical-align: top; border-right: 1px solid #eee;">
<div style="font-size: 8px; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; color: #999; margin-bottom: 6px;">Organisateur</div>
{% if logoBase64 %}
<img src="{{ logoBase64 }}" alt="Logo" style="width: 30px; height: 30px; vertical-align: middle; margin-right: 6px;">
{% endif %}
<span style="font-size: 12px; font-weight: bold;">{{ organizer.companyName ?? (organizer.firstName ~ ' ' ~ organizer.lastName) }}</span>
{% if organizer.siret %}
<div style="font-size: 8px; color: #666; margin-top: 4px;">SIRET: {{ organizer.siret }}</div>
{% endif %}
{% if organizer.address %}
<div style="font-size: 8px; color: #666;">{{ organizer.address }}{% if organizer.postalCode %}, {{ organizer.postalCode }}{% endif %}{% if organizer.city %} {{ organizer.city }}{% endif %}</div>
{% endif %}
{% if organizer.email %}
<div style="font-size: 8px; color: #666;">{{ organizer.email }}</div>
{% endif %}
{% if organizer.phone %}
<div style="font-size: 8px; color: #666;">{{ organizer.phone }}</div>
{% endif %}
{% if organizer.website %}
<div style="font-size: 8px; color: #666;">{{ organizer.website }}</div>
{% endif %}
</td>
<td style="width: 50%; padding: 14px 30px 14px 15px; vertical-align: top;">
<div style="font-size: 8px; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; color: #999; margin-bottom: 6px;">A propos de l'evenement</div>
{% if event.description %}
<div style="font-size: 9px; color: #444; line-height: 1.4; max-height: 60px; overflow: hidden;">{{ event.description|striptags }}</div>
{% endif %}
<table style="margin-top: 8px; border-collapse: collapse;">
<tr>
<td style="vertical-align: middle; padding-right: 8px;">
<img src="{{ eventQrBase64 }}" alt="QR" style="width: 50px; height: 50px;">
</td>
<td style="vertical-align: middle;">
<div style="font-size: 7px; font-weight: bold; text-transform: uppercase; color: #999;">Page evenement</div>
<div style="font-size: 8px; color: {{ ac }}; word-break: break-all;">{{ eventUrl }}</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- INFORMATIONS PRATIQUES + CONDITIONS -->
<div style="padding: 12px 30px; border-top: 1px solid #eee; background: #fafafa;">
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td style="width: 50%; vertical-align: top; padding-right: 15px;">
<div style="font-size: 10px; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; color: #999; margin-bottom: 4px;">Informations pratiques</div>
<div style="font-size: 9px; color: #555; line-height: 1.5;">
&bull; Il est recommande d'arriver en avance.<br>
&bull; En arrivant, preparez votre billet pour accelerer les controles a l'entree.<br>
&bull; A l'approche des controles de securite, merci de preparer vos affaires pour faciliter la verification.
</div>
</td>
<td style="width: 50%; vertical-align: top; padding-left: 15px;">
<div style="font-size: 10px; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; color: #999; margin-bottom: 4px;">Conditions generales</div>
<div style="font-size: 9px; color: #555; line-height: 1.5;">
&bull; Billet non remboursable conformement a l'article L. 121-21-8 12&deg; du Code de la consommation (exception au droit de retractation pour les prestations de loisirs a date determinee).<br>
&bull; La revente de billets est strictement interdite.<br>
&bull; En cas d'infraction, le billet pourra etre annule sans remboursement.<br>
&bull; En cas de force majeure (annulation de l'evenement, attentat, catastrophe naturelle, etc.), une demande de remboursement pourra etre adressee aupres de l'organisateur.<br>
&bull; E-Ticket est une plateforme de billetterie et n'organise pas les evenements. Tout litige relatif a l'evenement, aux billets ou aux remboursements doit etre traite directement avec l'organisateur.
</div>
</td>
</tr>
</table>
</div>
<!-- FOOTER -->
<div class="footer">
<table class="footer-table">
<tr>
{% if logoBase64 %}
<td class="footer-logo" style="width: 54px; padding-right: 12px;">
<img src="{{ logoBase64 }}" alt="Logo">
</td>
{% endif %}
<td>
<div class="footer-org">{{ organizer.companyName ?? (organizer.firstName ~ ' ' ~ organizer.lastName) }}</div>
{% if organizer.siret %}
<div class="footer-details">SIRET: {{ organizer.siret }}</div>
{% endif %}
{% if organizer.address %}
<div class="footer-details">{{ organizer.address }}{% if organizer.postalCode %}, {{ organizer.postalCode }}{% endif %}{% if organizer.city %} {{ organizer.city }}{% endif %}</div>
{% endif %}
{% if organizer.email or organizer.phone %}
<div class="footer-details">{% if organizer.email %}{{ organizer.email }}{% endif %}{% if organizer.email and organizer.phone %}{% endif %}{% if organizer.phone %}{{ organizer.phone }}{% endif %}</div>
{% endif %}
</td>
<td class="footer-powered">E-Ticket<br>by E-Cosplay</td>
</tr>
</table>
</div>
</body>
</html>