Trackily Docs

Discounts

TL;DR : un code promo (table discount_codes) est soit un pourcentage (type='percent'), soit un montant fixe (type='fixed'), soit free shipping. Tu peux le limiter à un produit, à N usages, à une plage de dates, ou à un panier minimal. Au checkout, le code est appliqué après le subtotal et avant la tax.

Liste des codes promo

Concept

Un code promo dans Trackily Commerce native est un levier de conversion : tu envoies "LAUNCH20" à ta liste email pour le lancement, tu écris "SUMMER15" dans ta bio Instagram, tu offres "VIP10" à tes meilleurs clients. Le visiteur le tape au checkout, le total recalcule en live, il convertit.

Côté code, c'est une ligne discount_codes lue par /api/order au moment du checkout. La validation est server-side : pas de moyen pour un visiteur malin d'éditer le DOM pour s'offrir un -50 %.

Anatomie d'un code (discount_codes)

Champ Type Rôle
id SERIAL identifiant interne
code TEXT UNIQUE le texte que le visiteur tape ("LAUNCH20"). Case-insensitive à la lecture mais stocké en l'état
type TEXT percent (default), fixed, free_shipping
value NUMERIC(12,2) pourcentage (10 = -10 %) OU montant fixe (10 = -10) — selon type
currency TEXT ISO-3 ou chaîne vide. Pour type='fixed' : la devise dans laquelle le montant est exprimé
product_id INTEGER FK NULL NULL = applicable à tout le catalogue ; non-NULL = limité à ce produit
min_order_amount NUMERIC(12,2) seuil minimum de panier pour que le code s'applique
max_uses INTEGER NULL nombre total d'utilisations possibles ; NULL = illimité
current_uses INTEGER compteur incrémenté atomiquement à chaque redemption
valid_from TIMESTAMPTZ date d'activation ; NULL = actif tout de suite
valid_to TIMESTAMPTZ date d'expiration ; NULL = pas d'expiration
is_active BOOLEAN flag d'activation manuel (override les dates)
description TEXT note interne pour toi-même
metadata JSONB extension libre

Anatomie d'une redemption (discount_redemptions)

À chaque utilisation réussie, une ligne audit est créée :

Champ Rôle
code_id FK vers discount_codes
order_id FK vers orders (SET NULL si la commande est supprimée)
discount_amount montant économisé par le client
currency devise de la commande
created_at timestamp

Utile pour les rapports : "combien LAUNCH20 a-t-il généré en CA ?" ou "qui a utilisé VIP10 ?".

Atomicité du compteur

Le compteur current_uses n'est pas incrémenté naïvement avec UPDATE … SET current_uses = current_uses + 1. Si deux acheteurs tapent le code en même temps sur un max_uses=1, ils pourraient l'utiliser tous les deux. Trackily exécute :

UPDATE discount_codes
   SET current_uses = current_uses + 1
 WHERE id = $1
   AND (max_uses IS NULL OR current_uses < max_uses)
RETURNING id;

Si la RETURNING ne ramène rien, le helper rollback la commande complète et renvoie "code limit reached". Idempotent et concurrence-safe.

Snapshot dans la commande

Trois colonnes ajoutées à orders :

  • discount_code (TEXT) — le code tapé, snapshoté (survit à la suppression du discount_code).
  • discount_amount (NUMERIC) — le montant réellement déduit.
  • discount_code_id (INTEGER FK NULL) — pointeur, SET NULL si suppression du code.

Les rapports peuvent grouper par discount_code (texte) même quand le code source a été supprimé.

Les trois types de codes

type='percent'

value=20 → -20 % sur le subtotal.

Exemple : panier de 100 €, code -20 % → discount_amount=20 €, total avant shipping/tax = 80 €.

Borné à 100 (un -150 % serait absurde). 100 = produit gratuit (rare cas valide pour une commande de service avec frais de port).

type='fixed'

value=10 + currency='EUR' → -10 € sur le subtotal.

Si le subtotal est inférieur au discount fixe, le discount est plafonné au subtotal (jamais de discount négatif). Ne touche pas le shipping.

type='free_shipping'

value ignoré. Au checkout, le shipping_cost est forcé à 0. Pratique si tu fais des promos genre "Livraison offerte ce week-end".

Combiner avec un min_order_amount est puissant : "Livraison gratuite à partir de 50 €" devient un upsell payant.

Comment faire (UI + MCP)

Via l'admin UI

  1. Commerce → Discount Codes (ou /admin#discount-codes).
  2. + Create code.
  3. Remplis :
    • Code (par exemple "LAUNCH20").
    • Type (Percent / Fixed / Free shipping).
    • Value (20).
    • Currency (si Fixed).
    • Product (None = global, ou pick un produit dans le dropdown).
    • Min order amount (optionnel — par exemple 30).
    • Max uses (optionnel — par exemple 100).
    • Valid from / Valid to (optionnels — calendrier picker).
    • Active (cocher par défaut).
    • Description (note interne, par exemple "Lancement Memo Mind 12 mai").
  4. Save. Le code apparaît dans la liste, prêt à être tapé au checkout.

L'écran liste affiche current_uses / max_uses, le total revenu généré (somme des discount_redemptions.discount_amount × orders.payment_status='paid'), et un bouton "Copy code" pour partager rapidement.

Via MCP

Lister :

{
  "name": "list_discount_codes",
  "arguments": {
    "store_id": null,
    "is_active": true,
    "limit": 50
  }
}

store_id=null cible le commerce native (les codes natifs n'ont pas de store_id). Pour les codes Shopify, passe le store_id cible.

Créer :

{
  "name": "create_discount_code",
  "arguments": {
    "code": "LAUNCH20",
    "type": "percent",
    "value": 20,
    "min_order_amount": 30,
    "max_uses": 100,
    "valid_from": "2026-05-15T00:00:00Z",
    "valid_to":   "2026-05-31T23:59:59Z",
    "description": "Lancement Memo Mind 12 mai"
  }
}

Tier-2 : preview + confirm_token, puis re-soumission avec le token. Préviens-toi avant d'émettre un code en production.

Supprimer :

{
  "name": "delete_discount_code",
  "arguments": { "code_id": 8 }
}

Idem Tier-2. La suppression DELETE la ligne (les redemptions historiques sont conservées via ON DELETE SET NULL).

Exemples concrets

1. Code de lancement à durée limitée

{
  "name": "create_discount_code",
  "arguments": {
    "code": "LAUNCH20",
    "type": "percent",
    "value": 20,
    "max_uses": 200,
    "valid_from": "2026-05-15T00:00:00Z",
    "valid_to":   "2026-05-22T23:59:59Z",
    "description": "1ère semaine lancement"
  }
}

200 redemptions ou 7 jours, whichever first. Au-delà, le code dit "Discount limit reached" ou "Discount code expired".

2. Code VIP réservé à un produit

{
  "name": "create_discount_code",
  "arguments": {
    "code": "VIP-MEMO",
    "type": "fixed",
    "value": 15,
    "currency": "EUR",
    "product_id": 12,
    "min_order_amount": 50,
    "max_uses": null,
    "description": "Code VIP, panier >= 50 €, sur Memo Mind seulement"
  }
}

Réutilisable à l'infini (max_uses=null), mais limité au produit 12 et à un panier mini de 50 €. Le bon code à mettre en signature d'un email post-purchase.

3. Livraison gratuite ce week-end

{
  "name": "create_discount_code",
  "arguments": {
    "code": "FREESHIP",
    "type": "free_shipping",
    "value": 0,
    "min_order_amount": 25,
    "valid_from": "2026-05-25T00:00:00Z",
    "valid_to":   "2026-05-26T23:59:59Z",
    "description": "Free shipping week-end 25-26 mai (panier >= 25 €)"
  }
}

Pas de coût direct pour toi (tu absorbes les 5 € de shipping), conversion typiquement +15 à 25 % sur les visiteurs hésitants.

4. Code one-shot personnel pour un client mécontent

{
  "name": "create_discount_code",
  "arguments": {
    "code": "SORRY-MARIE-001",
    "type": "percent",
    "value": 100,
    "max_uses": 1,
    "valid_to": "2026-06-15T23:59:59Z",
    "description": "Geste commercial Marie L. — commande 4287 cassée en transit"
  }
}

Code unique, -100 %, expire dans un mois, traçable.

Stratégies discount

  • Évite l'over-discount permanent — un code "WELCOME10" affiché en haut de la home apprend aux visiteurs à toujours chercher un code avant d'acheter. Préfère les codes ponctuels avec date d'expiration courte.
  • Code par canalEMAIL15, IG10, YT20 te permettent de mesurer le ROAS par canal sans dépendre d'UTM.
  • Code post-abandon — déclenche via l'automizer un email "Code -10 % valable 24h" envoyé J+1 aux paniers abandonnés. Conversion typique 12-18 %.
  • Code first-purchasemin_order_amount à votre AOV cible, code envoyé après inscription newsletter. Augmente l'AOV et la liste email en même temps.
  • Ne combine pas plusieurs codes — Trackily n'autorise qu'un code par commande. Combat les abus, garde le calcul simple.

Erreurs courantes

  • "Discount code not found" — le visiteur tape "LAUNCH20" mais le code en base est "launch20". La lookup applique LOWER() des deux côtés, donc ça devrait marcher. Vérifie qu'il n'y a pas un espace en début ou fin du champ code en base.
  • "Discount code expired"valid_to est dans le passé, ou valid_from est dans le futur, ou is_active=false.
  • "Discount limit reached"current_uses >= max_uses. Pour relever la limite, edit max_uses (les utilisations passées comptent toujours).
  • "Minimum order amount not met" — le subtotal du panier est sous min_order_amount. Affiché côté client comme "Add 12 € more to use this code".
  • "Discount not applicable to this product" — le code a un product_id spécifique et le visiteur essaie de l'appliquer sur un autre produit.
  • "Discount appliqué à 0" — pour un type='fixed' avec currency différente du panier, Trackily refuse silencieusement (pas de conversion auto entre devises). Crée un code par devise.
  • "Le compteur n'avance pas" — la commande est restée en pending. Le compteur n'avance qu'à la création de la commande, pas au paiement effectif. Si tu veux la métrique "redemptions payées", regarde plutôt via JOIN discount_redemptions × orders.payment_status='paid'.

Voir aussi

  • orders.md — où vivent discount_code, discount_amount, discount_code_id sur la commande.
  • Email → Sequences — déclencher un code post-abandon.
  • Automizer → Rules — créer / supprimer des codes en batch.
  • MCP create_price_rule — pour les Shopify-stores, équivalent automatique (sans code).