Reviews
TL;DR : tes acheteurs peuvent noter un produit de 1 à 5 étoiles et laisser un commentaire depuis
/p/<slug>. Les reviews tombent enstatus='pending', tu les modères dans l'admin, le badgeverified_purchaseest calculé à l'insertion en cherchant une commande avec le même email.

Concept
Les reviews sont la preuve sociale la plus efficace pour transformer un visiteur tiède en acheteur. Trackily intègre un système complet de collecte, modération et affichage des avis clients (Phase 17, table product_reviews).
Pas besoin de Trustpilot, pas de plugin Yotpo, pas de tracking pixel tiers. Le visiteur soumet sa review sur la même page que celle où il a acheté, tu la modères en deux clics, elle apparaît sous le produit.
Anatomie d'une review (product_reviews)
| Champ | Type | Rôle |
|---|---|---|
id |
SERIAL | identifiant interne |
native_product_id |
INTEGER FK | produit noté |
customer_email |
TEXT | email du client (clé du dédoublonnage et du verified_purchase) |
customer_name |
TEXT | nom affiché publiquement |
rating |
SMALLINT 1-5 | note (CHECK strict) |
title |
TEXT | titre court de la review |
body |
TEXT | corps du commentaire (markdown léger autorisé) |
status |
TEXT | pending / approved / rejected |
verified_purchase |
BOOLEAN | calculé à l'INSERT — true si une commande existe avec le même email pour ce produit |
ip |
TEXT | conservé pour anti-abuse |
user_agent |
TEXT | idem |
created_at |
TIMESTAMPTZ | date de soumission |
moderated_at |
TIMESTAMPTZ | date de décision admin |
moderator_user_id |
INTEGER FK | utilisateur qui a modéré |
Trois garde-fous anti-spam
- Rate limiting par IP via
pixelRateLimit(le même middleware qui protège les pixels de tracking). Une IP qui soumet trop de reviews en peu de temps est ralentie côté serveur. - Une review par couple (produit, email) grâce à l'index unique :
Re-soumettre depuis le même email met à jour la review existante (le code applicatif fait un upsert), il n'en crée pas une nouvelle. Pratique pour un client qui veut revenir sur son avis après plus d'expérience produit.CREATE UNIQUE INDEX idx_reviews_one_per_buyer ON product_reviews(native_product_id, LOWER(customer_email)); - Gate
allowed_domains: si le produit a unallowed_domainsnon vide, le POST/api/reviewsest refusé quand la requête arrive depuis un host hors whitelist. Tu évites qu'un concurrent qui clone ta landing puisse seeder des reviews sous tonproduct_id.
Modération par défaut
status='pending' à l'INSERT. La review n'est pas publique tant qu'un admin n'a pas cliqué "Approve". Trois choix dans le moderation queue :
- Approve → status passe à
approved, la review apparaît sur/p/<slug>et sur/api/reviews/product/:id. - Reject → status passe à
rejected. La review reste en base pour audit mais n'est plus visible. - Delete → suppression définitive de la ligne.
Le mode "auto-approval" n'est pas activé par défaut, parce qu'un système d'avis non-modéré est rapidement empoisonné par les concurrents et les bots. Si tu veux malgré tout l'activer pour un produit particulier (déjà connu, faible risque), tu peux flipper un flag dans les settings (commerce_reviews_auto_approve). Recommandé seulement quand tu reçois 50+ reviews / semaine et que la modération devient un goulet.
verified_purchase : comment c'est calculé
À l'INSERT de la review, le code applicatif exécute :
SELECT EXISTS (
SELECT 1
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
WHERE LOWER(o.customer_email) = LOWER($1)
AND oi.product_id = $2
AND o.payment_status IN ('paid', 'refunded', 'partial_refund')
) AS verified;
Le flag est sauvé sur la review (donc immutable après coup — sauf si tu le re-calcules manuellement). Tous les statuts paiement qui prouvent un achat (paid, refunded, partial_refund) comptent. Les commandes encore pending ne déclenchent pas le verified — un acheteur qui poste une review avant que son paiement n'aboutisse aura un badge non-verified ; il devra repost s'il veut le badge (ou tu peux flipper le bit à la main en base).
Affichage frontal : un badge "✓ Verified purchase" en vert sous le nom du reviewer.
Comment faire (UI + MCP)
Côté client : soumettre une review
Le formulaire est rendu en bas de /p/<slug>. Le visiteur :
- Clique sur les 5 étoiles (1 à 5).
- Tape un titre (court — 60 chars max recommandés).
- Tape un commentaire (max 2000 chars).
- Renseigne son nom et email.
- Submit →
POST /api/reviews. - Voit "Merci, votre avis sera publié après modération."
L'endpoint :
POST /api/reviews
Content-Type: application/json
{
"product_id": 12,
"customer_email": "marie@example.com",
"customer_name": "Marie L.",
"rating": 5,
"title": "Effet ressenti dès la deuxième semaine",
"body": "Je dors mieux et je retiens mieux ce que je lis. Bravo."
}
Réponse :
{ "ok": true, "review_id": 87, "verified_purchase": true }
Côté admin : modérer
- Navigue vers Reviews dans la sidebar (ou tape
/admin#native-reviews). - Tu vois la queue triée par défaut sur les
status='pending'. Filter par produit, par rating, par verified. - Pour chaque ligne, trois boutons : Approve / Reject / Delete.
- Le bouton Approve all from this email te permet de white-list un client de confiance (toutes ses reviews pending passent à approved en un clic).
Côté MCP
Pas d'outil MCP dédié aux reviews dans l'inventaire actuel — la collection se fait via le formulaire public, la modération via l'admin UI. Si tu as besoin de scripter (par exemple importer 50 reviews depuis Trustpilot), passe par l'admin REST :
POST /admin/api/reviews/bulk-import
Body: { "product_id": 12, "reviews": [ {...}, {...} ] }
…à l'aide d'un script Node interne, le endpoint est protégé par authMiddleware.
Endpoints publics
| Méthode | URL | Rôle |
|---|---|---|
POST |
/api/reviews |
soumettre une review (rate-limité, allowed_domains gate) |
GET |
/api/reviews/product/:id |
lister les reviews approuvées + aggregate (count, avg) |
GET /api/reviews/product/:id réponse type :
{
"ok": true,
"reviews": [
{
"id": 87,
"customer_name": "Marie L.",
"rating": 5,
"title": "Effet ressenti…",
"body": "Je dors mieux…",
"verified_purchase": true,
"created_at": "2026-05-12T08:43:21Z"
}
],
"count": 24,
"avg": 4.6,
"distribution": { "1": 1, "2": 0, "3": 2, "4": 5, "5": 16 }
}
Utile si tu veux embed les reviews dans un autre site (widget JS qui fetch cet endpoint).
Exemples concrets
1. Workflow de lancement
Tu lances Memo Mind. Premiers 30 jours :
- Tu envoies un email post-purchase (séquence J+15, J+25) qui pointe sur
/p/memo-mind#reviews-formavec un lien direct ancre. - 18 % des clients laissent un avis (moyenne typique pour les suppléments). 80 % en 5 étoiles, 15 % en 4, 5 % en 3 ou moins.
- Tu modères tout au début pour ne pas laisser passer un troll ; tu approuves 95 % des soumissions, tu rejettes les 5 % manifestement spam.
- Tu actives
commerce_reviews_auto_approveune fois passé le cap de 50 reviews validées — tu ne modères plus que les 1 ou 2 étoiles (alerté par une rule Automizer).
2. Reviews comme social proof sur la landing AI
Quand generate_landing_for_product génère une landing, le template inclut un slot "Reviews" qui pull les 3 dernières reviews approuvées avec rating ≥ 4. Le slot affiche aussi l'aggregate (par exemple "4.6 / 5 — 124 avis vérifiés"). Si tu n'as pas encore de reviews, le slot est masqué automatiquement.
3. Detect anomaly : pluie de mauvaises reviews
Crée une rule Automizer :
- Trigger :
product_reviews.rating <= 2 - Condition : >= 3 reviews ≤ 2 étoiles soumises dans les dernières 24h sur le même
native_product_id. - Action : webhook Slack vers ton channel #alerts + pause la campagne attachée au produit (
pause_campaign).
Voir Automizer → Rules pour l'implémentation détaillée.
Erreurs courantes
- "La review n'apparaît pas malgré l'approve" — vérifie que le produit n'est pas en
status='draft'(les reviews approuvées d'un produit draft ne s'affichent pas non plus, parce que la page produit n'est pas accessible). - "Le verified_purchase est false alors que le client a acheté" — le verified est calculé à l'INSERT. Si le client a acheté après avoir laissé sa review, le flag reste false. Re-poster la review depuis le même email mettra à jour la ligne et re-calculera le flag.
- "Index unique violation" — le client soumet une review pour un produit où il a déjà une review (sous le même email). Le code applicatif fait un upsert, donc l'erreur ne devrait pas remonter. Si elle remonte, c'est probablement parce que tu attaques l'endpoint à la main avec une casse différente sur l'email.
- "Reviews rejetées par allowed_domains" — le produit a une whitelist non vide et le visiteur arrive depuis un host non listé. Ajoute le host à
allowed_domainsou retire la whitelist. - "Spam massif soudain" — la rate limiting IP n'arrête qu'un IP à la fois. Si tu te fais arroser depuis un botnet, passe le produit en
status='draft'quelques heures (les soumissions sont refusées 404), nettoie en masse via SQLDELETE FROM product_reviews WHERE created_at > X AND status='pending', ajoute un CAPTCHA front-end le temps d'investiguer. - "Comment supprimer définitivement une review insultante ?" —
Deletedans l'admin UI fait un DELETE physique. Pas de soft-delete sur cette table.
Voir aussi
- products.md — la fiche produit qui porte
allowed_domains. - orders.md — l'origine du flag
verified_purchase. - Email → Sequences — la séquence post-purchase qui sollicite la review.
- Automizer → Rules — alerte automatique sur mauvaises reviews.