Mailix / templates /checkout.html
ernestmindres's picture
Upload 17 files
21f6d0f verified
<!DOCTYPE html>
<html lang="fr" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="https://i.imgur.com/7Gn3toV.png">
<title>Paiement - Nexus Pro</title>
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0" />
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://js.stripe.com/v3/"></script>
<style>
/* Variables de Thème - Couleurs Noir Foncé (Mode Sombre par défaut) */
:root, html.dark {
--bg-page: #181818; /* Noir très foncé, pas de bleu marine */
--bg-card: #282828;
--text-main: #EAEAEA;
--text-secondary: #B0B0B0;
--border-color: #404040;
--shadow-color: rgba(0, 0, 0, 0.5);
}
/* Mode Clair */
html.light {
--bg-page: #F5F5F5;
--bg-card: #FFFFFF;
--text-main: #202020;
--text-secondary: #5C5C5C;
--border-color: #E0E0E0;
--shadow-color: rgba(0, 0, 0, 0.1);
}
/* Application des variables */
body {
background-color: var(--bg-page);
color: var(--text-main);
transition: background-color 0.3s, color 0.3s;
}
.bg-gray-900 { background-color: var(--bg-page) !important; }
.text-white { color: var(--text-main) !important; }
.bg-gray-800, .bg-gray-800\/50 {
background-color: var(--bg-card) !important;
box-shadow: 0 4px 6px -1px var(--shadow-color), 0 2px 4px -2px var(--shadow-color);
}
.text-gray-400 { color: var(--text-secondary) !important; }
.border-gray-700, .border-gray-800 { border-color: var(--border-color) !important; }
.text-gray-300 { color: var(--text-secondary) !important; } /* Utilisation de la même couleur pour uniformité */
.bg-gray-700 { background-color: #404040 !important; }
.theme-switch {
background: none;
border: none;
cursor: pointer;
color: var(--text-main);
font-size: 24px;
transition: color 0.2s;
padding: 0.5rem; /* Espace autour du bouton */
}
/* Style pour l'élément Stripe Elements (input) */
#card-element {
padding: 1rem;
border: 1px solid var(--border-color);
background-color: var(--bg-card);
border-radius: 0.5rem;
margin-bottom: 1rem;
color: var(--text-main); /* Assurez-vous que le texte par défaut est lisible */
}
</style>
</head>
<body class="min-h-screen">
<div class="container mx-auto p-4 sm:p-6 lg:p-8 max-w-7xl">
<header class="sticky top-0 z-50 flex flex-col sm:flex-row justify-between items-center py-4 border-b border-blue-500/50 mb-8 bg-gray-900" style="background-color: var(--bg-page); transition: background-color 0.3s;">
<a href="/" class="text-4xl font-extrabold text-blue-500 tracking-wider mb-4 sm:mb-0">
NEXUS
</a>
<nav class="flex items-center space-x-4">
<a href="/documentation" class="px-4 py-2 text-white hover:text-blue-300 transition duration-200 font-medium">
Documentation
</a>
<a href="/a-propos" class="px-4 py-2 text-white hover:text-blue-300 transition duration-200 font-medium">
À Propos
</a>
<button id="theme-toggle" class="theme-switch" aria-label="Basculer le thème">
<span class="material-symbols-rounded">light_mode</span>
</button>
{% if is_logged_in %}
<a href="/dashboard" class="px-4 py-2 bg-blue-600 border border-blue-600 hover:bg-blue-700 font-medium transition duration-200">
Dashboard
</a>
{% else %}
<a href="/connexion" class="px-4 py-2 bg-blue-600 border border-blue-600 hover:bg-blue-700 font-medium transition duration-200">
Connexion
</a>
{% endif %}
</nav>
</header>
<main class="flex flex-col items-center">
<div class="max-w-xl w-full p-6 sm:p-8 bg-gray-800 shadow-2xl border-t-4 border-blue-500">
<h1 class="text-3xl font-extrabold text-white mb-6 text-center">
Finaliser votre Commande
</h1>
<div class="mb-8 border border-gray-700 p-4">
<h2 class="text-xl font-bold text-white mb-3 border-b border-gray-700 pb-2">
Récapitulatif
</h2>
<div class="space-y-2 text-gray-300">
<div class="flex justify-between">
<span>Plan Sélectionné:</span>
<span class="font-semibold text-white">{{ plan.title }}</span>
</div>
<div class="text-sm italic text-gray-400">
{{ plan.description }}
</div>
<div class="flex justify-between pt-2 border-t border-gray-700 font-bold text-lg text-yellow-400">
<span>Total (TVA incluse):</span>
<span>{{ plan.price_display }}</span>
</div>
</div>
</div>
<form id="payment-form">
<h2 class="text-xl font-bold text-white mb-4">
Informations de Paiement
</h2>
<div id="card-element" class="shadow-md">
</div>
<div id="card-errors" role="alert" class="text-red-500 text-sm mb-4"></div>
<button id="submit-button" class="w-full px-6 py-3 bg-blue-600 text-white text-lg font-bold shadow-md hover:bg-blue-700 transition duration-300" type="submit">
Payer maintenant {{ plan.price_display }}
</button>
</form>
</div>
</main>
<footer class="mt-12 pt-6 border-t border-gray-700 text-center text-gray-500 text-sm">
&copy; 2025 Nexus. Paiement Sécurisé par Stripe.
</footer>
</div>
<script>
// Logique de bascule du mode sombre/clair (à inclure pour l'uniformité)
const themeToggle = document.getElementById('theme-toggle');
const htmlElement = document.documentElement;
const iconElement = themeToggle.querySelector('.material-symbols-rounded');
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
htmlElement.classList.remove('dark');
htmlElement.classList.add('light');
iconElement.textContent = 'dark_mode';
} else {
htmlElement.classList.add('dark');
htmlElement.classList.remove('light');
iconElement.textContent = 'light_mode';
}
themeToggle.addEventListener('click', () => {
if (htmlElement.classList.contains('light')) {
htmlElement.classList.remove('light');
htmlElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
iconElement.textContent = 'light_mode';
} else {
htmlElement.classList.add('light');
htmlElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
iconElement.textContent = 'dark_mode';
}
});
// =======================================================
// LOGIQUE D'INTÉGRATION STRIPE ELEMENTS
// =======================================================
const planId = new URLSearchParams(window.location.search).get('plan');
const stripe = Stripe("{{ stripe_public_key }}"); // Clé publique injectée par Jinja
const elements = stripe.elements();
const cardElement = elements.create('card', {
style: {
base: {
color: htmlElement.classList.contains('light') ? '#202020' : '#EAEAEA',
fontFamily: 'system-ui, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: htmlElement.classList.contains('light') ? '#5C5C5C' : '#B0B0B0',
},
},
invalid: {
color: '#EF4444',
iconColor: '#EF4444',
},
}
});
cardElement.mount('#card-element');
const form = document.getElementById('payment-form');
const submitButton = document.getElementById('submit-button');
const cardErrors = document.getElementById('card-errors');
let clientSecret = null; // Pour stocker le Client Secret reçu du backend
// 1. Récupérer le Client Secret du backend au chargement de la page
async function fetchClientSecret() {
try {
// Requête au backend pour créer le PaymentIntent
const response = await fetch('/api/create-payment-intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ plan_id: planId })
});
if (!response.ok) {
throw new Error('Erreur lors de la création du PaymentIntent.');
}
const data = await response.json();
clientSecret = data.client_secret;
} catch (error) {
console.error('Erreur Stripe:', error);
cardErrors.textContent = "Erreur: Impossible d'initialiser le paiement. Veuillez réessayer.";
submitButton.disabled = true;
}
}
// 2. Traiter la soumission du formulaire
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (!clientSecret) {
cardErrors.textContent = "Veuillez patienter pendant l'initialisation du paiement.";
return;
}
submitButton.disabled = true;
submitButton.textContent = 'Traitement en cours...';
// Confirmer le paiement côté client avec le clientSecret et l'élément Stripe
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
// Idéalement, les détails de l'utilisateur (email/nom) devraient être fournis ici
},
}
});
if (error) {
// Afficher les erreurs (ex: carte refusée, données invalides)
cardErrors.textContent = error.message;
submitButton.disabled = false;
submitButton.textContent = 'Payer maintenant {{ plan.price_display }}';
} else if (paymentIntent.status === 'succeeded') {
// Le paiement a réussi
window.location.href = `/dashboard?payment=success&intent=${paymentIntent.id}`;
}
});
// Démarrage : Récupérer le client secret au chargement
fetchClientSecret();
</script>
</body>
</html>