- Add billing fields to User (isBilling, billingAmount, billingState, billingStripeSubscriptionId) and OrganizerInvitation (billingAmount) - Registration: organizer gets billingState="poor" (pending review) - Admin approval: sets isBilling=true, billingAmount from form, state="good" - Invitation: billingAmount from invitation, if 0 then isBilling=false - ROLE_ROOT accounts: billing free (amount=0, state="good") - Block Stripe Connect creation and all organizer features if state is "poor" or "suspendu" - Hide Stripe configuration section if billing not settled - Add billing checkout via Stripe subscription with success route - Webhooks: checkout.session.completed activates billing, invoice.payment_failed and customer.subscription.deleted suspend account and disable online events - Show billing alert on /mon-compte with amount and subscribe button - Display billing info in invitation email and landing page - Add email templates for billing activated/failed/cancelled Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
234 lines
16 KiB
Twig
234 lines
16 KiB
Twig
{% extends 'base.html.twig' %}
|
|
|
|
{% block title %}Invitation - {{ invitation.companyName }} - E-Ticket{% endblock %}
|
|
|
|
{% block body %}
|
|
<div class="bg-[#fbfbfb] overflow-x-hidden italic font-sans">
|
|
|
|
<section class="relative bg-white border-b-8 border-gray-900 px-4 pt-20 pb-16">
|
|
<div class="absolute inset-0 opacity-[0.03] pointer-events-none select-none overflow-hidden">
|
|
<span class="text-[8rem] md:text-[20rem] font-black uppercase leading-none block -rotate-12 translate-y-10">INVITATION</span>
|
|
</div>
|
|
|
|
<div class="max-w-3xl mx-auto relative z-10 text-center">
|
|
<div class="inline-block px-4 py-1 border-3 border-gray-900 bg-[#fabf04] font-black uppercase text-xs tracking-widest mb-6 shadow-[4px_4px_0px_rgba(0,0,0,1)]">Invitation organisateur</div>
|
|
<h1 class="text-4xl md:text-6xl font-black uppercase tracking-tighter leading-[0.85] mb-4">{{ invitation.companyName }}</h1>
|
|
<p class="text-lg font-bold text-gray-600">Bonjour {{ invitation.firstName }}, vous etes invite(e) a rejoindre <strong>E-Ticket</strong>.</p>
|
|
</div>
|
|
</section>
|
|
|
|
{% if invitation.offer or invitation.commissionRate is not null %}
|
|
<section class="bg-gray-900 text-white py-8 px-4">
|
|
<div class="max-w-3xl mx-auto text-center">
|
|
<p class="text-xs font-black uppercase tracking-widest text-[#fabf04] mb-2">Votre offre</p>
|
|
<p class="text-2xl font-black uppercase tracking-tighter">
|
|
{% if invitation.offer == 'free' %}Gratuit{% elseif invitation.offer == 'basic' %}Basic{% elseif invitation.offer == 'custom' %}Sur-mesure{% else %}{{ invitation.offer }}{% endif %}
|
|
</p>
|
|
{% if invitation.commissionRate is not null %}
|
|
<p class="text-sm font-bold text-gray-400 mt-1">Taux de commission E-Ticket : {{ invitation.commissionRate }}% <span class="text-gray-500">(hors frais Stripe)</span></p>
|
|
{% endif %}
|
|
{% if invitation.billingAmount is not null %}
|
|
<p class="text-sm font-bold mt-3">
|
|
{% if invitation.billingAmount == 0 %}
|
|
<span class="text-green-400">Aucun abonnement mensuel — utilisation gratuite</span>
|
|
{% else %}
|
|
<span class="text-[#fabf04]">Abonnement mensuel : {{ (invitation.billingAmount / 100)|number_format(2, ',', ' ') }} €/mois</span>
|
|
{% endif %}
|
|
</p>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if invitation.message %}
|
|
<section class="py-8 px-4">
|
|
<div class="max-w-3xl mx-auto">
|
|
<div class="border-4 border-gray-900 bg-white p-6 shadow-[6px_6px_0px_rgba(0,0,0,1)]">
|
|
<p class="text-xs font-black uppercase tracking-widest text-gray-400 mb-3">Message de l'equipe</p>
|
|
<p class="text-lg font-bold text-gray-700 leading-relaxed">{{ invitation.message }}</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
|
|
<section class="py-12 px-4">
|
|
<div class="max-w-3xl mx-auto">
|
|
<h2 class="text-3xl font-black uppercase tracking-tighter text-center mb-8">Decouvrir E-Ticket by E-Cosplay</h2>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-12">
|
|
<div class="border-4 border-gray-900 p-6 bg-white shadow-[6px_6px_0px_rgba(0,0,0,1)]">
|
|
<div class="text-3xl mb-3">🎫</div>
|
|
<h3 class="font-black uppercase text-sm tracking-widest mb-2">Evenements</h3>
|
|
<p class="text-sm font-bold text-gray-600">Creez et gerez vos evenements en quelques clics. Billetterie, brocantes, votes.</p>
|
|
</div>
|
|
<div class="border-4 border-gray-900 p-6 bg-white shadow-[6px_6px_0px_rgba(0,0,0,1)]">
|
|
<div class="text-3xl mb-3">💳</div>
|
|
<h3 class="font-black uppercase text-sm tracking-widest mb-2">Paiement securise</h3>
|
|
<p class="text-sm font-bold text-gray-600">Paiement en ligne via Stripe avec encaissement direct sur votre compte connect.</p>
|
|
</div>
|
|
<div class="border-4 border-gray-900 p-6 bg-white shadow-[6px_6px_0px_rgba(0,0,0,1)]">
|
|
<div class="text-3xl mb-3">📈</div>
|
|
<h3 class="font-black uppercase text-sm tracking-widest mb-2">Statistiques</h3>
|
|
<p class="text-sm font-bold text-gray-600">Suivez vos ventes en temps reel, commandes, billets vendus et chiffre d'affaires.</p>
|
|
</div>
|
|
<div class="border-4 border-gray-900 p-6 bg-white shadow-[6px_6px_0px_rgba(0,0,0,1)]">
|
|
<div class="text-3xl mb-3">🎟</div>
|
|
<h3 class="font-black uppercase text-sm tracking-widest mb-2">Billets PDF</h3>
|
|
<p class="text-sm font-bold text-gray-600">Generez des billets PDF personnalises avec QR code, envoyes automatiquement par email.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<h2 class="text-2xl font-black uppercase tracking-tighter text-center mb-6">Comment ca fonctionne ?</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
|
|
<div class="text-center">
|
|
<div class="w-12 h-12 border-4 border-gray-900 bg-[#fabf04] flex items-center justify-center mx-auto mb-3 font-black text-xl shadow-[4px_4px_0px_rgba(0,0,0,1)]">1</div>
|
|
<h3 class="font-black uppercase text-xs tracking-widest mb-2">Creez votre compte</h3>
|
|
<p class="text-xs font-bold text-gray-500">Configurez votre profil organisateur et connectez votre compte Stripe.</p>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="w-12 h-12 border-4 border-gray-900 bg-[#fabf04] flex items-center justify-center mx-auto mb-3 font-black text-xl shadow-[4px_4px_0px_rgba(0,0,0,1)]">2</div>
|
|
<h3 class="font-black uppercase text-xs tracking-widest mb-2">Publiez vos evenements</h3>
|
|
<p class="text-xs font-bold text-gray-500">Ajoutez vos evenements, categories et billets. Mettez en ligne en un clic.</p>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="w-12 h-12 border-4 border-gray-900 bg-[#fabf04] flex items-center justify-center mx-auto mb-3 font-black text-xl shadow-[4px_4px_0px_rgba(0,0,0,1)]">3</div>
|
|
<h3 class="font-black uppercase text-xs tracking-widest mb-2">Vendez et encaissez</h3>
|
|
<p class="text-xs font-bold text-gray-500">Les ventes arrivent directement sur votre compte Stripe. Billets generes automatiquement.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<h2 class="text-2xl font-black uppercase tracking-tighter text-center mb-6">Les offres</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
|
|
<div class="border-4 border-gray-900 p-6 bg-white shadow-[6px_6px_0px_rgba(0,0,0,1)] {{ invitation.offer == 'free' ? 'ring-4 ring-[#fabf04]' : '' }}">
|
|
<h3 class="font-black uppercase text-sm tracking-widest mb-1">Gratuit</h3>
|
|
{% if invitation.offer == 'free' %}<span class="inline-block px-2 py-0.5 bg-[#fabf04] border-2 border-gray-900 text-[10px] font-black uppercase mb-3">Votre offre</span>{% endif %}
|
|
<ul class="space-y-2 text-xs font-bold text-gray-600 mt-3">
|
|
<li class="text-green-600">✓ 1 evenement</li>
|
|
<li class="text-green-600">✓ Billets standards</li>
|
|
<li class="text-green-600">✓ QR code</li>
|
|
<li class="text-gray-300">✕ Image par billet</li>
|
|
<li class="text-gray-300">✕ Design personnalise</li>
|
|
<li class="text-gray-300">✕ Generation PDF</li>
|
|
</ul>
|
|
</div>
|
|
<div class="border-4 border-gray-900 p-6 bg-white shadow-[6px_6px_0px_rgba(0,0,0,1)] {{ invitation.offer == 'basic' ? 'ring-4 ring-[#fabf04]' : '' }}">
|
|
<h3 class="font-black uppercase text-sm tracking-widest mb-1">Basic</h3>
|
|
{% if invitation.offer == 'basic' %}<span class="inline-block px-2 py-0.5 bg-[#fabf04] border-2 border-gray-900 text-[10px] font-black uppercase mb-3">Votre offre</span>{% endif %}
|
|
<ul class="space-y-2 text-xs font-bold text-gray-600 mt-3">
|
|
<li class="text-green-600">✓ Evenements illimites</li>
|
|
<li class="text-green-600">✓ Billets standards</li>
|
|
<li class="text-green-600">✓ QR code</li>
|
|
<li class="text-green-600">✓ Generation PDF</li>
|
|
<li class="text-gray-300">✕ Image par billet</li>
|
|
<li class="text-gray-300">✕ Design personnalise</li>
|
|
</ul>
|
|
</div>
|
|
<div class="border-4 border-gray-900 p-6 bg-white shadow-[6px_6px_0px_rgba(0,0,0,1)] {{ invitation.offer == 'custom' ? 'ring-4 ring-[#fabf04]' : '' }}">
|
|
<h3 class="font-black uppercase text-sm tracking-widest mb-1">Sur-mesure</h3>
|
|
{% if invitation.offer == 'custom' %}<span class="inline-block px-2 py-0.5 bg-[#fabf04] border-2 border-gray-900 text-[10px] font-black uppercase mb-3">Votre offre</span>{% endif %}
|
|
<ul class="space-y-2 text-xs font-bold text-gray-600 mt-3">
|
|
<li class="text-green-600">✓ Evenements illimites</li>
|
|
<li class="text-green-600">✓ Design personnalise</li>
|
|
<li class="text-green-600">✓ Image par billet</li>
|
|
<li class="text-green-600">✓ QR code</li>
|
|
<li class="text-green-600">✓ Generation PDF</li>
|
|
<li class="text-green-600">✓ Categories illimitees</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<h2 class="text-2xl font-black uppercase tracking-tighter text-center mb-6">Commissions</h2>
|
|
<div class="border-4 border-gray-900 p-6 bg-white shadow-[6px_6px_0px_rgba(0,0,0,1)]">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="text-center">
|
|
<p class="text-xs font-black uppercase tracking-widest text-gray-400 mb-1">Commission E-Ticket</p>
|
|
<p class="text-4xl font-black text-indigo-600">{{ invitation.commissionRate ?? 3 }}%</p>
|
|
<p class="text-xs font-bold text-gray-500 mt-1">Par transaction sur le montant HT</p>
|
|
</div>
|
|
<div class="text-center">
|
|
<p class="text-xs font-black uppercase tracking-widest text-gray-400 mb-1">Commission Stripe</p>
|
|
<p class="text-4xl font-black text-gray-900">1.5% + 0.25 €</p>
|
|
<p class="text-xs font-bold text-gray-500 mt-1">Frais standard cartes europeennes</p>
|
|
</div>
|
|
</div>
|
|
{% set rate = invitation.commissionRate ?? 3 %}
|
|
<div class="mt-6 pt-4 border-t-2 border-gray-200 overflow-x-auto">
|
|
<table class="w-full text-xs font-bold" style="min-width: 500px;">
|
|
<thead>
|
|
<tr class="border-b-2 border-gray-900">
|
|
<th class="py-2 text-left text-gray-400 uppercase tracking-widest">Prix billet</th>
|
|
<th class="py-2 text-right text-gray-400 uppercase tracking-widest">E-Ticket ({{ rate }}%)</th>
|
|
<th class="py-2 text-right text-gray-400 uppercase tracking-widest">Stripe</th>
|
|
<th class="py-2 text-right text-gray-400 uppercase tracking-widest">Total frais</th>
|
|
<th class="py-2 text-right text-green-600 uppercase tracking-widest">Vous recevez</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for price in [1, 2, 5, 10, 15, 20] %}
|
|
{% set eticket_fee = price * rate / 100 %}
|
|
{% set stripe_fee = price * 0.015 + 0.25 %}
|
|
{% set total_fee = eticket_fee + stripe_fee %}
|
|
{% set net = price - total_fee %}
|
|
<tr class="border-b border-gray-100">
|
|
<td class="py-2 text-left">{{ price|number_format(2, ',', ' ') }} €</td>
|
|
<td class="py-2 text-right text-red-600">{{ eticket_fee|number_format(2, ',', ' ') }} €</td>
|
|
<td class="py-2 text-right text-red-600">{{ stripe_fee|number_format(2, ',', ' ') }} €</td>
|
|
<td class="py-2 text-right text-red-600">{{ total_fee|number_format(2, ',', ' ') }} €</td>
|
|
<td class="py-2 text-right text-green-600 font-black">{{ net|number_format(2, ',', ' ') }} €</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{% if expired|default(false) %}
|
|
<section class="py-12 px-4 border-t-8 border-gray-900 bg-white">
|
|
<div class="max-w-xl mx-auto text-center">
|
|
<div class="text-5xl mb-4 text-red-600">⏱</div>
|
|
<p class="font-black uppercase text-lg tracking-tighter">Invitation expiree</p>
|
|
<p class="text-sm font-bold text-gray-500 mt-2">Cette invitation a expire. Veuillez contacter l'administrateur pour en recevoir une nouvelle.</p>
|
|
</div>
|
|
</section>
|
|
{% elseif invitation.status in ['sent', 'opened'] %}
|
|
<section class="py-12 px-4 border-t-8 border-gray-900 bg-white">
|
|
<div class="max-w-xl mx-auto text-center">
|
|
<h2 class="text-2xl font-black uppercase tracking-tighter mb-6">Votre reponse</h2>
|
|
|
|
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
|
<form method="post" action="{{ path('app_invitation_respond', {token: invitation.token, action: 'accept'}) }}">
|
|
<button type="submit" class="w-full sm:w-auto px-8 py-4 border-4 border-gray-900 bg-[#fabf04] font-black uppercase text-sm tracking-widest shadow-[6px_6px_0px_rgba(0,0,0,1)] hover:shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:translate-y-[-2px] transition-all cursor-pointer">
|
|
Accepter l'invitation
|
|
</button>
|
|
</form>
|
|
<form method="post" action="{{ path('app_invitation_respond', {token: invitation.token, action: 'refuse'}) }}">
|
|
<button type="submit" class="w-full sm:w-auto px-8 py-4 border-4 border-gray-900 bg-white font-black uppercase text-sm tracking-widest shadow-[6px_6px_0px_rgba(0,0,0,1)] hover:bg-red-600 hover:text-white transition-all cursor-pointer">
|
|
Refuser
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
{% else %}
|
|
<section class="py-12 px-4 border-t-8 border-gray-900 bg-white">
|
|
<div class="max-w-xl mx-auto text-center">
|
|
{% if invitation.status == 'accepted' %}
|
|
<div class="text-5xl mb-4 text-green-600">✓</div>
|
|
<p class="font-black uppercase text-lg tracking-tighter">Invitation acceptee</p>
|
|
<p class="text-sm font-bold text-gray-500 mt-2">Votre compte sera bientot active.</p>
|
|
{% elseif invitation.status == 'refused' %}
|
|
<div class="text-5xl mb-4 text-gray-400">✕</div>
|
|
<p class="font-black uppercase text-lg tracking-tighter">Invitation refusee</p>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
|
|
<section class="py-8 px-4 bg-gray-900 text-white text-center">
|
|
<p class="text-xs font-bold uppercase tracking-widest opacity-50">E-Ticket by E-Cosplay — Plateforme de billetterie en ligne</p>
|
|
</section>
|
|
</div>
|
|
{% endblock %}
|