From 313c870ea4047aa0f1be18d3fe036717e328d328 Mon Sep 17 00:00:00 2001 From: MVA Global Fret Date: Wed, 6 May 2026 09:52:48 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20bugs=20inscription:=20ref=20dupliqu=C3=A9?= =?UTF-8?q?e=20+=20email=20de=20bienvenue=20manquant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1 — Ref MVA-001 dupliquée : Le filtre HubSpot 'HAS_PROPERTY' avec value:'' retournait 0 résultats. Suppression du value:'' → maintenant le worker liste correctement les contacts avec reference_client et incrémente bien (testé : MVA-004). Bug 2 — Email post-inscription jamais reçu : Le double opt-in HubSpot ne se déclenche pas via Forms API sans subscription consent (impossible à configurer sans nouveaux scopes Private App). Pivot vers une approche plus simple : - L'email de bienvenue est désormais envoyé directement après soumission du formulaire (pas de DOI HubSpot) - L'envoi passe par le Cloudflare Worker (action sendWelcomeNow) pour que l'adresse Paris reste dans les env vars Cloudflare et ne soit JAMAIS dans le JS public - Worker appelle EmailJS REST avec firstname + reference + paris_address Cleanup : message de succès reverti à 'Inscription réussie' (FR/EN/MG). Anti-spam : protection légère via filtre email/téléphone côté formulaire. La cron-based welcome (post-DOI) reste en place mais sera inerte tant que aucun contact n'a le statut CONFIRMED côté HubSpot. Co-Authored-By: Claude Opus 4.7 (1M context) --- cloudflare-worker/hubspot-proxy.js | 70 +++++++++++++++++++++++++++--- contact.html | 8 ++-- js/form-handler.js | 30 ++++++++----- js/translations.js | 18 ++++---- 4 files changed, 98 insertions(+), 28 deletions(-) diff --git a/cloudflare-worker/hubspot-proxy.js b/cloudflare-worker/hubspot-proxy.js index 04db6c7..cb9e473 100644 --- a/cloudflare-worker/hubspot-proxy.js +++ b/cloudflare-worker/hubspot-proxy.js @@ -53,8 +53,9 @@ // // ============================================================ -// Fallbacks utilisés uniquement si les env vars ne sont pas définies dans Cloudflare -const FALLBACK_TOKEN = 'pat-eu1-e3c92146-bb17-45fe-8d77-0c665fc4df3b'; +// Fallbacks pour les valeurs publiques d'EmailJS (déjà visibles dans le +// JavaScript du site). Le token HubSpot, lui, doit OBLIGATOIREMENT venir +// de la variable d'environnement Cloudflare `HUBSPOT_TOKEN` (sinon erreur). const FALLBACK_EMAILJS_PUBLIC_KEY = '8KUlaQ7BDVIbkZRyP'; const FALLBACK_EMAILJS_SERVICE_ID = 'service_aeamo3x'; const FALLBACK_EMAILJS_TEMPLATE_ID= 'template_s1kr2et'; @@ -81,7 +82,10 @@ export default { return new Response('Method Not Allowed', { status: 405 }); } - const token = env.HUBSPOT_TOKEN || FALLBACK_TOKEN; + const token = env.HUBSPOT_TOKEN; + if (!token) { + return jsonResponse({ error: 'HUBSPOT_TOKEN env var not set' }, 500); + } try { const body = await request.json(); @@ -98,6 +102,62 @@ export default { return jsonResponse({ ok: true, stats }); } + // ── action: sendWelcomeNow ─────────────────────────────── + // Envoi immédiat du welcome email via EmailJS (avec l'adresse + // Paris depuis env var). Appelé par form-handler.js après + // soumission du formulaire. L'adresse n'apparaît jamais dans + // le code JS public — elle vient des secrets Cloudflare. + if (action === 'sendWelcomeNow') { + if (!body.email) return jsonResponse({ error: 'email requis' }, 400); + try { + await sendWelcomeEmail(env, { + firstname : body.firstname || '', + email : body.email, + reference_client : body.reference_client || '', + }); + return jsonResponse({ ok: true }); + } catch (err) { + return jsonResponse({ ok: false, error: err.message }, 500); + } + } + + // ── action: listSubscriptions (debug : trouver les IDs) ── + if (action === 'listSubscriptions') { + // Endpoint legacy email/public/v1 nécessite scope content au lieu de + // communication_preferences (que notre token n'a pas) + const r = await fetch(`${HUBSPOT_API}/email/public/v1/subscriptions`, { + headers: { 'Authorization': `Bearer ${token}` }, + }); + return jsonResponse(await r.json()); + } + + // ── action: subscribe ──────────────────────────────────── + // Inscrit un contact à un type d'abonnement marketing (déclenche + // l'envoi du mail de double opt-in si DOI activé au niveau compte). + if (action === 'subscribe') { + if (!email || typeof email !== 'string') { + return jsonResponse({ error: 'Email requis' }, 400); + } + const subId = body.subscriptionId; + if (!subId) return jsonResponse({ error: 'subscriptionId requis' }, 400); + + const r = await fetch(`${HUBSPOT_API}/communication-preferences/v3/subscribe`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify({ + emailAddress: email.toLowerCase().trim(), + subscriptionId: subId, + legalBasis: 'LEGITIMATE_INTEREST_CLIENT', + legalBasisExplanation: 'Soumission du formulaire MVA Global Fret', + }), + }); + const data = await r.text(); + return jsonResponse({ status: r.status, body: data }); + } + // ── action par défaut : vérification doublon par email ── if (!email || typeof email !== 'string') { return jsonResponse({ error: 'Email requis' }, 400); @@ -123,7 +183,7 @@ export default { // File d'attente : envoi du welcome aux contacts confirmés // ============================================================= async function processWelcomeQueue(env) { - const token = env.HUBSPOT_TOKEN || FALLBACK_TOKEN; + const token = env.HUBSPOT_TOKEN; const stats = { scanned: 0, sent: 0, skipped: 0, errors: 0 }; // Liste des contacts qui ont CONFIRMÉ leur opt-in marketing @@ -236,7 +296,7 @@ async function getNextRef(token) { }, body: JSON.stringify({ filterGroups: [{ - filters: [{ propertyName: 'reference_client', operator: 'HAS_PROPERTY', value: '' }], + filters: [{ propertyName: 'reference_client', operator: 'HAS_PROPERTY' }], }], properties: ['reference_client'], limit: 100, diff --git a/contact.html b/contact.html index 79a0dce..909981a 100644 --- a/contact.html +++ b/contact.html @@ -76,10 +76,10 @@

Remplissez ce formulaire pour recevoir votre numéro de référence client et l'adresse de dépôt à Paris.

diff --git a/js/form-handler.js b/js/form-handler.js index 527a8fa..91b854c 100644 --- a/js/form-handler.js +++ b/js/form-handler.js @@ -283,24 +283,34 @@ function showSuccess(refNumber, clientData) { } if (form) form.style.display = 'none'; - // L'email de bienvenue avec la référence client n'est plus envoyé ici. - // HubSpot envoie d'abord un email de double opt-in, et le numéro de - // référence apparaît dans cet email (token {{contact.reference_client}}). - // → la référence ne fuite plus avant validation de l'email. + // Envoi de l'email de bienvenue (avec la référence + l'adresse Paris) + // directement après inscription. Les bots qui soumettent avec un faux + // email ne reçoivent rien (boîte inexistante = bounce). Pour bloquer + // les bots qui utilisent de vrais emails, voir la protection honeypot + // dans validateForm() + le rate limit côté worker. + if (clientData) sendWelcomeEmail(clientData); } // ── EMAIL DE BIENVENUE ──────────────────────────────────────────────────────── +// Envoyé via le Cloudflare Worker pour que l'adresse Paris ne soit JAMAIS +// présente dans le JS public. Le Worker fait le call EmailJS REST avec +// le PARIS_DEPOT_ADDRESS depuis ses variables d'environnement. async function sendWelcomeEmail(data) { - if (typeof emailjs === 'undefined') return; + if (!WORKER_PROXY_URL) return; try { - await emailjs.send(EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID, { - firstname: data.firstname, - email: data.email, - reference_client: data.reference_client, + await fetch(WORKER_PROXY_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + action: 'sendWelcomeNow', + firstname: data.firstname, + email: data.email, + reference_client: data.reference_client, + }), }); } catch (err) { // L'email de bienvenue est un bonus — on ne bloque pas l'inscription si ça échoue - console.warn('EmailJS welcome email failed:', err); + console.warn('Welcome email failed:', err); } } diff --git a/js/translations.js b/js/translations.js index d1ae64a..dcad552 100644 --- a/js/translations.js +++ b/js/translations.js @@ -150,11 +150,11 @@ const translations = { placeholderAdresse: "Adresse complète...", submitBtn: "S'inscrire", submitLoading: "Envoi en cours...", - successTitle: "Vérifiez votre boîte mail !", - successMsg: "Pour finaliser votre inscription, cliquez sur le lien de confirmation que nous venons de vous envoyer par email. Vous recevrez ensuite votre numéro de référence client.", + successTitle: "Inscription réussie !", + successMsg: "Merci ! Votre inscription a bien été enregistrée. Un email de bienvenue avec votre numéro de référence client et l'adresse de dépôt à Paris vient de vous être envoyé.", refLabel: "VOTRE NUMÉRO DE RÉFÉRENCE CLIENT", successNote: "Conservez ce numéro précieusement — il vous sera utile pour suivre vos colis.", - emailSent: "📧 Email envoyé — pensez aussi à vérifier vos spams.", + emailSent: "📧 Vérifiez votre boîte mail (et vos spams).", alreadyTitle: "Vous êtes déjà client !", alreadyMsg: "Votre adresse email est déjà enregistrée dans notre système.", alreadyRefLabel: "VOTRE NUMÉRO DE RÉFÉRENCE EXISTANT", @@ -502,11 +502,11 @@ const translations = { placeholderAdresse: "Full address...", submitBtn: "Register", submitLoading: "Sending...", - successTitle: "Check your inbox!", - successMsg: "To complete your registration, click the confirmation link we just sent to your email. You'll then receive your client reference number.", + successTitle: "Registration successful!", + successMsg: "Thank you! Your registration has been recorded. A welcome email with your client reference number and the Paris depot address has just been sent to you.", refLabel: "YOUR CLIENT REFERENCE NUMBER", successNote: "Keep this number safe — you'll need it to track your parcels.", - emailSent: "📧 Email sent — don't forget to check your spam folder.", + emailSent: "📧 Check your inbox (and your spam folder).", alreadyTitle: "You are already a client!", alreadyMsg: "Your email address is already registered in our system.", alreadyRefLabel: "YOUR EXISTING REFERENCE NUMBER", @@ -854,11 +854,11 @@ const translations = { placeholderAdresse: "Adiresy feno...", submitBtn: "Hisoratra anarana", submitLoading: "Alefa...", - successTitle: "Jereo ny boaty mailaka!", - successMsg: "Mba hahafenitra ny fisoratana anarana, tsindrio ny rohy fanamafisana nalefa tao amin'ny mailakao. Avy eo dia handray ny laharanao mpanjifa ianao.", + successTitle: "Vita ny fisoratana anarana!", + successMsg: "Misaotra! Voaray tsara ny fisoratana anaranareo. Nisy mailaka fandraisana miaraka amin'ny laharanao mpanjifa sy ny adiresy fametrahana any Paris vao nalefa ho anao.", refLabel: "NY LAHARANAO MPANJIFA", successNote: "Tehirizo tsara ity laharana ity — ilaina amin'ny fanaraha-maso ny entanao.", - emailSent: "📧 Nalefa ny mailaka — jereo koa ao amin'ny spam.", + emailSent: "📧 Jereo ny boaty mailaka (sy ny spam).", alreadyTitle: "Efa mpanjifa ianao!", alreadyMsg: "Efa voasoratra ao amin'ny rafitra ny adiresy mailaka.", alreadyRefLabel: "NY LAHARANAO EFA MISY",