COLLECTOR ACCOUNT

Promotion !
0,01 € 100,00 €
Référence de l'article: DE 1 A 1000 EUROS MAXIMUM

<!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>VX — Compte & Collector</title> <style> body { margin: 0; font-family: Arial, sans-serif; background: #080b12; color: white; } .vx-container { width: min(900px, 92%); margin: 40px auto; } .vx-panel { background: #111827; border: 1px solid #263043; border-radius: 20px; padding: 25px; margin-bottom: 20px; } input, button { box-sizing: border-box; border-radius: 10px; padding: 12px; border: 0; } input { width: 100%; margin: 6px 0; background: #1f2937; color: white; } button { cursor: pointer; font-weight: bold; } .vx-button { width: 100%; margin-top: 10px; color: white; background: linear-gradient(135deg,#2563eb,#7c3aed); } .vx-button:hover { transform: translateY(-1px); } .vx-balance { font-size: 25px; font-weight: bold; } .vx-status { color: #9ca3af; } .vx-status.ok { color: #22c55e; } .vx-status.no { color: #ef4444; } /* COLLECTION */ .collector-grid { display: grid; grid-template-columns: repeat(auto-fit,minmax(180px,1fr)); gap: 15px; } .collector-card { text-align: center; padding: 20px; border-radius: 18px; background: #0f172a; border: 1px solid #293548; } .collector-logo { width: 90px; height: 90px; margin: auto; display: flex; align-items: center; justify-content: center; border-radius: 50%; font-size: 25px; font-weight: bold; background: linear-gradient( 135deg, #a855f7, #2563eb ); } .collector-card button { margin-top: 10px; width: 100%; color: white; background: #2563eb; } .collector-card button:disabled { background: #374151; cursor: not-allowed; } .small { font-size: 13px; color: #9ca3af; } </style> </head> <body> <div class="vx-container"> <!-- ========================== COMPTE ========================== --> <section class="vx-panel"> <h1>Compte VX</h1> <input id="username" type="text" placeholder="Nom d'utilisateur" autocomplete="username" > <input id="email" type="email" placeholder="Adresse e-mail" autocomplete="email" > <button class="vx-button" onclick="createAccount()" > CRÉER MON COMPTE </button> <p id="accountMessage" class="vx-status"> </p> </section> <!-- ========================== STATUT ========================== --> <section class="vx-panel"> <h2>Statut du compte</h2> <p> Utilisateur : <strong id="displayUsername">—</strong> </p> <p> Statut 18+ : <strong id="ageStatus" class="vx-status no"> Non vérifié </strong> </p> <p> Monnaie VX : </p> <div class="vx-balance"> <span id="vxBalance">0</span> VX </div> </section> <!-- ========================== VÉRIFICATION ========================== --> <section class="vx-panel"> <h2>Vérification d'âge</h2> <p class="small"> Cette démonstration ne collecte aucune pièce d'identité ni donnée biométrique. </p> <button class="vx-button" onclick="setAgeVerified()" > CONFIRMER LA VÉRIFICATION 18+ </button> </section> <!-- ========================== COLLECTOR ========================== --> <section class="vx-panel"> <h2>✦ COLLECTOR</h2> <p class="small"> Échange tes VX contre des Xenodis. </p> <div id="collectorGrid" class="collector-grid"> </div> </section> </div> <script> /* ===================================================== CONFIGURATION ===================================================== */ const VX_STORAGE = "vx_user_account_v1"; /* ===================================================== CATALOGUE XENODIS ===================================================== */ const XENODIS = [ { id: "origin", name: "Xenodis Origin", price: 100, logo: "VX" }, { id: "nova", name: "Xenodis Nova", price: 500, logo: "✦" }, { id: "eclipse", name: "Xenodis Eclipse", price: 1500, logo: "◈" }, { id: "prime", name: "Xenodis Prime", price: 5000, logo: "VX+" }, { id: "infinity", name: "Xenodis Infinity", price: 15000, logo: "∞" }, { id: "one", name: "Xenodis One", price: 50000, logo: "Ⅰ" } ]; /* ===================================================== COMPTE PAR DÉFAUT ===================================================== */ function defaultUser() { return { id: null, username: null, email: null, ageVerified: false, vx: 0, collector: [] }; } /* ===================================================== CHARGER LE COMPTE ===================================================== */ function loadUser() { try { const saved = localStorage.getItem(VX_STORAGE); if (!saved) { return defaultUser(); } return { ...defaultUser(), ...JSON.parse(saved) }; } catch { return defaultUser(); } } let user = loadUser(); /* ===================================================== SAUVEGARDE ===================================================== */ function saveUser() { localStorage.setItem( VX_STORAGE, JSON.stringify(user) ); } /* ===================================================== CRÉATION DU COMPTE ===================================================== */ function createAccount() { const username = document .getElementById("username") .value .trim(); const email = document .getElementById("email") .value .trim(); if (!username || !email) { showMessage( "Complète les informations du compte.", true ); return; } if (!user.id) { user.id = crypto.randomUUID(); } user.username = username; user.email = email; saveUser(); updateInterface(); showMessage( "Compte créé avec succès." ); } /* ===================================================== VÉRIFICATION 18+ ===================================================== */ /* Dans un vrai site, cette valeur devrait provenir d'un processus de vérification fiable côté serveur. Le navigateur ne doit pas décider lui-même qu'une personne est majeure à partir d'une photo. */ function setAgeVerified() { user.ageVerified = true; saveUser(); updateInterface(); showMessage( "Statut 18+ enregistré." ); } /* ===================================================== AJOUTER DES VX ===================================================== */ /* Démonstration uniquement. Sur un vrai site, le solde doit être crédité côté serveur après une opération autorisée. */ function addVX(amount) { if (!Number.isFinite(amount) || amount <= 0) { return; } user.vx += amount; saveUser(); updateInterface(); } /* ===================================================== ACHAT XENODIS ===================================================== */ function buyXenodis(id) { if (!user.id) { alert( "Crée d'abord ton compte." ); return; } /* Protection d'accès : les échanges Collector nécessitent ici un statut 18+ vérifié. */ if (!user.ageVerified) { alert( "La vérification 18+ est nécessaire." ); return; } const item = XENODIS.find( x => x.id === id ); if (!item) { return; } if (user.collector.includes(id)) { alert( "Ce Xenodis est déjà dans ta collection." ); return; } if (user.vx < item.price) { alert( "Solde VX insuffisant." ); return; } /* ÉCHANGE */ user.vx -= item.price; user.collector.push(id); saveUser(); updateInterface(); alert( item.name + " ajouté à ta collection !" ); } /* ===================================================== AFFICHAGE COLLECTOR ===================================================== */ function renderCollector() { const grid = document.getElementById( "collectorGrid" ); grid.innerHTML = ""; XENODIS.forEach(item => { const owned = user.collector.includes( item.id ); const card = document.createElement( "article" ); card.className = "collector-card"; card.innerHTML = ` <div class="collector-logo"> ${item.logo} </div> <h3> ${item.name} </h3> <p> ${item.price.toLocaleString()} VX </p> <button ${owned ? "disabled" : ""} onclick="buyXenodis('${item.id}')" > ${ owned ? "✓ COLLECTIONNÉ" : "ÉCHANGER" } </button> `; grid.appendChild(card); }); } /* ===================================================== INTERFACE ===================================================== */ function updateInterface() { document .getElementById( "displayUsername" ) .textContent = user.username || "—"; document .getElementById( "vxBalance" ) .textContent = user.vx.toLocaleString(); const age = document.getElementById( "ageStatus" ); if (user.ageVerified) { age.textContent = "✓ 18+ vérifié"; age.className = "vx-status ok"; } else { age.textContent = "Non vérifié"; age.className = "vx-status no"; } renderCollector(); } /* ===================================================== MESSAGE ===================================================== */ function showMessage( message, error = false ) { const element = document.getElementById( "accountMessage" ); element.textContent = message; element.className = error ? "vx-status no" : "vx-status ok"; } /* ===================================================== INITIALISATION ===================================================== */ updateInterface(); </script> </body> </html>

 

AVEC CERTIFICATION COLLECTOR ACCOUNT 

 

// server.js const express = require("express"); const crypto = require("crypto"); const Database = require("better-sqlite3"); const app = express(); app.use(express.json()); const db = new Database("vx.db"); // -------------------------------------------------- // TABLES // -------------------------------------------------- db.exec(` CREATE TABLE IF NOT EXISTS references_vx ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT UNIQUE NOT NULL, user_id INTEGER NOT NULL, xenodis_id TEXT NOT NULL, used INTEGER DEFAULT 0, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS collector_exchange ( id TEXT PRIMARY KEY, user_id INTEGER NOT NULL, reference_id INTEGER NOT NULL, xenodis_id TEXT NOT NULL, vx_amount INTEGER NOT NULL, status TEXT NOT NULL, created_at INTEGER NOT NULL, confirmed_at INTEGER ); CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, vx_balance INTEGER DEFAULT 0 ); `); // -------------------------------------------------- // CATALOGUE OFFICIEL // NE JAMAIS prendre ces valeurs depuis le navigateur // -------------------------------------------------- const XENODIS = { origin: { name: "Origin", exchangeVX: 100 }, nova: { name: "Nova", exchangeVX: 500 }, eclipse: { name: "Eclipse", exchangeVX: 1500 }, prime: { name: "Prime", exchangeVX: 5000 }, infinity: { name: "Infinity", exchangeVX: 15000 }, one: { name: "One", exchangeVX: 50000 } }; // -------------------------------------------------- // CREATION D'UN DOSSIER D'ECHANGE // -------------------------------------------------- app.post("/api/collector/exchange/start", (req, res) => { const userId = req.body.userId; const referenceCode = String(req.body.reference || "").trim(); if (!userId || !referenceCode) { return res.status(400).json({ error: "Données manquantes" }); } // Recherche de la référence const ref = db.prepare(` SELECT * FROM references_vx WHERE code = ? AND user_id = ? `).get(referenceCode, userId); if (!ref) { return res.status(400).json({ error: "Référence invalide" }); } // Référence déjà utilisée if (ref.used) { return res.status(400).json({ error: "Cette référence a déjà été utilisée" }); } // Vérification du Xenodis const xenodis = XENODIS[ref.xenodis_id]; if (!xenodis) { return res.status(400).json({ error: "Xenodis inconnu" }); } // Identifiant unique de transaction const exchangeId = crypto.randomUUID(); const now = Date.now(); db.prepare(` INSERT INTO collector_exchange ( id, user_id, reference_id, xenodis_id, vx_amount, status, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?) `).run( exchangeId, userId, ref.id, ref.xenodis_id, xenodis.exchangeVX, "WAITING_CONFIRMATIONS", now ); return res.json({ success: true, exchangeId, security: { referenceValid: true, referenceUnused: true, xenodisValid: true, amountServerVerified: true }, exchange: { xenodis: xenodis.name, xenodisId: ref.xenodis_id, vx: xenodis.exchangeVX }, message: "Échange préparé. Trois confirmations sont nécessaires." }); }); // -------------------------------------------------- // CONFIRMATION 1 / 2 / 3 // -------------------------------------------------- app.post("/api/collector/exchange/confirm", (req, res) => { const userId = req.body.userId; const exchangeId = req.body.exchangeId; if (!userId || !exchangeId) { return res.status(400).json({ error: "Données manquantes" }); } const exchange = db.prepare(` SELECT * FROM collector_exchange WHERE id = ? AND user_id = ? `).get(exchangeId, userId); if (!exchange) { return res.status(404).json({ error: "Échange introuvable" }); } if (exchange.status === "COMPLETED") { return res.status(400).json({ error: "Échange déjà terminé" }); } if (exchange.status === "CANCELLED") { return res.status(400).json({ error: "Échange annulé" }); } // Récupération de la référence const ref = db.prepare(` SELECT * FROM references_vx WHERE id = ? `).get(exchange.reference_id); // Vérifications de sécurité if (!ref) { return res.status(400).json({ error: "Référence introuvable" }); } if (ref.used) { return res.status(400).json({ error: "Référence déjà utilisée" }); } if (ref.user_id !== userId) { return res.status(403).json({ error: "Référence non autorisée" }); } if (ref.xenodis_id !== exchange.xenodis_id) { return res.status(400).json({ error: "Le Xenodis ne correspond pas à la référence" }); } // Nombre de confirmations déjà effectuées let confirmations = 0; if (exchange.status === "WAITING_CONFIRMATIONS") { confirmations = 1; db.prepare(` UPDATE collector_exchange SET status = ? WHERE id = ? `).run( "CONFIRMATION_1", exchangeId ); } else if (exchange.status === "CONFIRMATION_1") { confirmations = 2; db.prepare(` UPDATE collector_exchange SET status = ? WHERE id = ? `).run( "CONFIRMATION_2", exchangeId ); } else if (exchange.status === "CONFIRMATION_2") { confirmations = 3; // -------------------------------------------------- // DERNIERE ETAPE : TRANSACTION ATOMIQUE // -------------------------------------------------- const transaction = db.transaction(() => { // Relecture de la référence const currentRef = db.prepare(` SELECT * FROM references_vx WHERE id = ? `).get(exchange.reference_id); if (!currentRef || currentRef.used) { throw new Error("REFERENCE_ALREADY_USED"); } // Marque la référence comme utilisée const result = db.prepare(` UPDATE references_vx SET used = 1 WHERE id = ? AND used = 0 `).run(exchange.reference_id); if (result.changes !== 1) { throw new Error("REFERENCE_LOCK_FAILED"); } // Crédit VX db.prepare(` UPDATE users SET vx_balance = vx_balance + ? WHERE id = ? `).run( exchange.vx_amount, userId ); // Echange terminé db.prepare(` UPDATE collector_exchange SET status = ?, confirmed_at = ? WHERE id = ? `).run( "COMPLETED", Date.now(), exchangeId ); }); try { transaction(); } catch (err) { return res.status(409).json({ error: "Échange impossible", code: err.message }); } return res.json({ success: true, confirmations: 3, status: "COMPLETED", exchangeId, message: "Échange Collector validé." }); } return res.json({ success: true, confirmations, required: 3, exchangeId, status: confirmations === 3 ? "COMPLETED" : "WAITING_CONFIRMATION" }); }); // -------------------------------------------------- // ANNULATION // -------------------------------------------------- app.post("/api/collector/exchange/cancel", (req, res) => { const { userId, exchangeId } = req.body; const result = db.prepare(` UPDATE collector_exchange SET status = 'CANCELLED' WHERE id = ? AND user_id = ? AND status != 'COMPLETED' `).run(exchangeId, userId); if (!result.changes) { return res.status(400).json({ error: "Impossible d'annuler cet échange" }); } res.json({ success: true, status: "CANCELLED" }); }); // -------------------------------------------------- app.listen(3000, () => { console.log("VX Collector sécurisé : http://localhost:3000"); });
 

Et côté interface, tu peux afficher les trois confirmations ainsi :

 
<div id="exchangeSecurity" hidden> <h2>🔐 Validation sécurisée</h2> <div id="exchangeDetails"></div> <p id="securityStep"> Vérification 1/3 </p> <button id="confirmExchange"> CONFIRMER </button> <button id="cancelExchange"> ANNULER </button> </div> <script> async function startCollectorExchange(reference) { const response = await fetch("/api/collector/exchange/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: CURRENT_USER_ID, reference }) }); const data = await response.json(); if (!response.ok) { alert(data.error); return; } window.currentExchange = data.exchangeId; document.getElementById("exchangeSecurity").hidden = false; document.getElementById("exchangeDetails").innerHTML = ` <strong>Xenodis :</strong> ${data.exchange.xenodis}<br> <strong>Référence :</strong> ${reference}<br> <strong>Crédit :</strong> ${data.exchange.vx} VX<br> <strong>Transaction :</strong> ${data.exchangeId} `; document.getElementById("securityStep").textContent = "Vérification 1/3"; } document.getElementById("confirmExchange") .addEventListener("click", async () => { const response = await fetch( "/api/collector/exchange/confirm", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: CURRENT_USER_ID, exchangeId: window.currentExchange }) } ); const data = await response.json(); if (!response.ok) { alert(data.error); return; } if (data.status === "COMPLETED") { document.getElementById("securityStep").textContent = "✅ Validation 3/3 — Échange terminé"; document.getElementById("confirmExchange").disabled = true; return; } document.getElementById("securityStep").textContent = `Validation ${data.confirmations + 1}/3`; }); document.getElementById("cancelExchange") .addEventListener("click", async () => { await fetch("/api/collector/exchange/cancel", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: CURRENT_USER_ID, exchangeId: window.currentExchange }) }); document.getElementById("exchangeSecurity").hidden = true; }); </script>

 

PROMOTION XENO 30% ACTIF