Fix bugs inscription: ref dupliquée + email de bienvenue manquant
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) <noreply@anthropic.com>
This commit is contained in:
parent
e1032b1405
commit
313c870ea4
@ -53,8 +53,9 @@
|
|||||||
//
|
//
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Fallbacks utilisés uniquement si les env vars ne sont pas définies dans Cloudflare
|
// Fallbacks pour les valeurs publiques d'EmailJS (déjà visibles dans le
|
||||||
const FALLBACK_TOKEN = 'pat-eu1-e3c92146-bb17-45fe-8d77-0c665fc4df3b';
|
// 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_PUBLIC_KEY = '8KUlaQ7BDVIbkZRyP';
|
||||||
const FALLBACK_EMAILJS_SERVICE_ID = 'service_aeamo3x';
|
const FALLBACK_EMAILJS_SERVICE_ID = 'service_aeamo3x';
|
||||||
const FALLBACK_EMAILJS_TEMPLATE_ID= 'template_s1kr2et';
|
const FALLBACK_EMAILJS_TEMPLATE_ID= 'template_s1kr2et';
|
||||||
@ -81,7 +82,10 @@ export default {
|
|||||||
return new Response('Method Not Allowed', { status: 405 });
|
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 {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
@ -98,6 +102,62 @@ export default {
|
|||||||
return jsonResponse({ ok: true, stats });
|
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 ──
|
// ── action par défaut : vérification doublon par email ──
|
||||||
if (!email || typeof email !== 'string') {
|
if (!email || typeof email !== 'string') {
|
||||||
return jsonResponse({ error: 'Email requis' }, 400);
|
return jsonResponse({ error: 'Email requis' }, 400);
|
||||||
@ -123,7 +183,7 @@ export default {
|
|||||||
// File d'attente : envoi du welcome aux contacts confirmés
|
// File d'attente : envoi du welcome aux contacts confirmés
|
||||||
// =============================================================
|
// =============================================================
|
||||||
async function processWelcomeQueue(env) {
|
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 };
|
const stats = { scanned: 0, sent: 0, skipped: 0, errors: 0 };
|
||||||
|
|
||||||
// Liste des contacts qui ont CONFIRMÉ leur opt-in marketing
|
// Liste des contacts qui ont CONFIRMÉ leur opt-in marketing
|
||||||
@ -236,7 +296,7 @@ async function getNextRef(token) {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
filterGroups: [{
|
filterGroups: [{
|
||||||
filters: [{ propertyName: 'reference_client', operator: 'HAS_PROPERTY', value: '' }],
|
filters: [{ propertyName: 'reference_client', operator: 'HAS_PROPERTY' }],
|
||||||
}],
|
}],
|
||||||
properties: ['reference_client'],
|
properties: ['reference_client'],
|
||||||
limit: 100,
|
limit: 100,
|
||||||
|
|||||||
@ -76,10 +76,10 @@
|
|||||||
<p style="color: var(--text-light); margin-bottom: 32px;" data-i18n="contact.formSubtitle">Remplissez ce formulaire pour recevoir votre numéro de référence client et l'adresse de dépôt à Paris.</p>
|
<p style="color: var(--text-light); margin-bottom: 32px;" data-i18n="contact.formSubtitle">Remplissez ce formulaire pour recevoir votre numéro de référence client et l'adresse de dépôt à Paris.</p>
|
||||||
|
|
||||||
<div class="form-success" id="formSuccess" role="alert">
|
<div class="form-success" id="formSuccess" role="alert">
|
||||||
<i class="fa-solid fa-envelope-circle-check" style="font-size: 2rem; color: var(--gold); display: block; margin-bottom: 12px;"></i>
|
<i class="fa-solid fa-circle-check" style="font-size: 2rem; color: var(--green); display: block; margin-bottom: 12px;"></i>
|
||||||
<strong data-i18n="contact.successTitle">Vérifiez votre boîte mail !</strong><br>
|
<strong data-i18n="contact.successTitle">Inscription réussie !</strong><br>
|
||||||
<span data-i18n="contact.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.</span>
|
<span data-i18n="contact.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é.</span>
|
||||||
<p style="margin-top:12px; font-size:0.85rem; color: var(--text-light);" data-i18n="contact.emailSent">📧 Email envoyé — pensez aussi à vérifier vos spams.</p>
|
<p style="margin-top:12px; font-size:0.85rem; color: var(--text-light);" data-i18n="contact.emailSent">📧 Vérifiez votre boîte mail (et vos spams).</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- DÉJÀ INSCRIT -->
|
<!-- DÉJÀ INSCRIT -->
|
||||||
|
|||||||
@ -283,24 +283,34 @@ function showSuccess(refNumber, clientData) {
|
|||||||
}
|
}
|
||||||
if (form) form.style.display = 'none';
|
if (form) form.style.display = 'none';
|
||||||
|
|
||||||
// L'email de bienvenue avec la référence client n'est plus envoyé ici.
|
// Envoi de l'email de bienvenue (avec la référence + l'adresse Paris)
|
||||||
// HubSpot envoie d'abord un email de double opt-in, et le numéro de
|
// directement après inscription. Les bots qui soumettent avec un faux
|
||||||
// référence apparaît dans cet email (token {{contact.reference_client}}).
|
// email ne reçoivent rien (boîte inexistante = bounce). Pour bloquer
|
||||||
// → la référence ne fuite plus avant validation de l'email.
|
// 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 ────────────────────────────────────────────────────────
|
// ── 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) {
|
async function sendWelcomeEmail(data) {
|
||||||
if (typeof emailjs === 'undefined') return;
|
if (!WORKER_PROXY_URL) return;
|
||||||
try {
|
try {
|
||||||
await emailjs.send(EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID, {
|
await fetch(WORKER_PROXY_URL, {
|
||||||
firstname: data.firstname,
|
method: 'POST',
|
||||||
email: data.email,
|
headers: { 'Content-Type': 'application/json' },
|
||||||
reference_client: data.reference_client,
|
body: JSON.stringify({
|
||||||
|
action: 'sendWelcomeNow',
|
||||||
|
firstname: data.firstname,
|
||||||
|
email: data.email,
|
||||||
|
reference_client: data.reference_client,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// L'email de bienvenue est un bonus — on ne bloque pas l'inscription si ça échoue
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -150,11 +150,11 @@ const translations = {
|
|||||||
placeholderAdresse: "Adresse complète...",
|
placeholderAdresse: "Adresse complète...",
|
||||||
submitBtn: "S'inscrire",
|
submitBtn: "S'inscrire",
|
||||||
submitLoading: "Envoi en cours...",
|
submitLoading: "Envoi en cours...",
|
||||||
successTitle: "Vérifiez votre boîte mail !",
|
successTitle: "Inscription réussie !",
|
||||||
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.",
|
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",
|
refLabel: "VOTRE NUMÉRO DE RÉFÉRENCE CLIENT",
|
||||||
successNote: "Conservez ce numéro précieusement — il vous sera utile pour suivre vos colis.",
|
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 !",
|
alreadyTitle: "Vous êtes déjà client !",
|
||||||
alreadyMsg: "Votre adresse email est déjà enregistrée dans notre système.",
|
alreadyMsg: "Votre adresse email est déjà enregistrée dans notre système.",
|
||||||
alreadyRefLabel: "VOTRE NUMÉRO DE RÉFÉRENCE EXISTANT",
|
alreadyRefLabel: "VOTRE NUMÉRO DE RÉFÉRENCE EXISTANT",
|
||||||
@ -502,11 +502,11 @@ const translations = {
|
|||||||
placeholderAdresse: "Full address...",
|
placeholderAdresse: "Full address...",
|
||||||
submitBtn: "Register",
|
submitBtn: "Register",
|
||||||
submitLoading: "Sending...",
|
submitLoading: "Sending...",
|
||||||
successTitle: "Check your inbox!",
|
successTitle: "Registration successful!",
|
||||||
successMsg: "To complete your registration, click the confirmation link we just sent to your email. You'll then receive your client reference number.",
|
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",
|
refLabel: "YOUR CLIENT REFERENCE NUMBER",
|
||||||
successNote: "Keep this number safe — you'll need it to track your parcels.",
|
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!",
|
alreadyTitle: "You are already a client!",
|
||||||
alreadyMsg: "Your email address is already registered in our system.",
|
alreadyMsg: "Your email address is already registered in our system.",
|
||||||
alreadyRefLabel: "YOUR EXISTING REFERENCE NUMBER",
|
alreadyRefLabel: "YOUR EXISTING REFERENCE NUMBER",
|
||||||
@ -854,11 +854,11 @@ const translations = {
|
|||||||
placeholderAdresse: "Adiresy feno...",
|
placeholderAdresse: "Adiresy feno...",
|
||||||
submitBtn: "Hisoratra anarana",
|
submitBtn: "Hisoratra anarana",
|
||||||
submitLoading: "Alefa...",
|
submitLoading: "Alefa...",
|
||||||
successTitle: "Jereo ny boaty mailaka!",
|
successTitle: "Vita ny fisoratana anarana!",
|
||||||
successMsg: "Mba hahafenitra ny fisoratana anarana, tsindrio ny rohy fanamafisana nalefa tao amin'ny mailakao. Avy eo dia handray ny laharanao mpanjifa ianao.",
|
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",
|
refLabel: "NY LAHARANAO MPANJIFA",
|
||||||
successNote: "Tehirizo tsara ity laharana ity — ilaina amin'ny fanaraha-maso ny entanao.",
|
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!",
|
alreadyTitle: "Efa mpanjifa ianao!",
|
||||||
alreadyMsg: "Efa voasoratra ao amin'ny rafitra ny adiresy mailaka.",
|
alreadyMsg: "Efa voasoratra ao amin'ny rafitra ny adiresy mailaka.",
|
||||||
alreadyRefLabel: "NY LAHARANAO EFA MISY",
|
alreadyRefLabel: "NY LAHARANAO EFA MISY",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user