Trackily Docs

Commerce native

TL;DR : vends tes propres produits physiques ou digitaux directement depuis Trackily, sans Shopify, sans WooCommerce — checkout, paiement, livraison, remboursement, tout est intégré.

Liste des produits natifs

Concept

Le module Commerce native transforme Trackily en plateforme e-commerce complète. Tu sors la carte bancaire de tes visiteurs sans dépendre d'un Shopify externe : un produit créé dans Trackily devient instantanément vendable sur n'importe quelle landing page de ton compte, et chaque commande remonte dans la même base que tes clics, tes leads et tes conversions affiliate.

C'est volontairement un Shopify-like simplifié, pas un clone. L'objectif n'est pas de couvrir 100 % des cas d'usage d'un retailer multi-canal, mais de couvrir les 90 % qui comptent pour un media buyer qui veut tester un produit en direct sans payer la dîme à un SaaS tiers :

  • 1 produit physique ou digital avec variantes (taille, couleur…)
  • 1 landing AI avec bloc checkout intégré
  • 1 compte Stripe ou PayPal connecté (en un clic depuis la Phase 18)
  • 1 zone de shipping géo + tarifs
  • 1 code promo de lancement
  • Une page /p/<slug> standalone si tu veux vendre sans landing complète
  • Un catalogue /catalog si tu veux servir plusieurs produits sur le même domaine

Tout le reste — emails post-purchase, abandoned cart, reviews, fulfillment, refunds — s'empile par-dessus sans que tu doives changer d'outil.

Quand utiliser Commerce native vs intégration Shopify

Trackily supporte deux mondes en parallèle :

Critère Commerce native Intégration Shopify
Tu vends en direct ? oui non (Shopify héberge)
Catalogue native_products (DB Trackily) sync depuis stores (Shopify)
Checkout Bloc checkout sur la landing OU page /p/<slug> Cart Shopify (off-site)
Paiements Stripe Connect / PayPal multi-compte Géré par Shopify
Attribution click vers vente Native (orders.click_id) Webhook Shopify vers conversions
Variantes Jusqu'à 3 axes, prix + stock par variante Calquées sur Shopify
Reviews, discounts, shipping zones Tout en interne Délégué à Shopify
Inventaire Stock par variante (-1 = illimité) Sync inventory levels
Idéal pour Tester un produit, mini-store, dropshipping affilié Site marchand existant, gros catalogue

Tu peux faire tourner les deux en parallèle. Une campagne peut pointer soit sur un native_product_id, soit sur un offer_id affilié, soit sur un product_id Shopify-synchronisé. Les trois chemins remontent dans le même reporting unifié.

Le reste de cette section couvre uniquement le commerce native. Pour la partie Shopify, voir la section dédiée dans docs/admin-ui/.

Architecture et data model

Voici le schéma simplifié des tables qui font tourner le commerce native. Tu n'as jamais besoin d'écrire de SQL — c'est juste pour comprendre ce qui se passe sous le capot quand tu cliques.

                    ┌──────────────────────┐
                    │   native_products    │  ← le produit (nom, type, currency)
                    │  (slug, status,      │
                    │   category, images,  │
                    │   tax_rate,          │
                    │   shipping_cost,     │
                    │   allowed_domains)   │
                    └─────────┬────────────┘
                              │ 1..N
            ┌─────────────────┴─────────────────────┐
            ▼                                       ▼
┌──────────────────────────┐         ┌──────────────────────────┐
│ native_product_options   │         │ native_product_variants  │  ← une ligne
│ (Color, Size, Material)  │         │ (option1/2/3_value,      │     par
│  position 0/1/2, values) │         │  price, sku, stock,      │     combinaison
└──────────────────────────┘         │  image_url, is_active)   │
                                     └────────────┬─────────────┘
                                                  │ référencé par
                                                  ▼
            ┌──────────────────────────┐    ┌──────────────────────────┐
            │       landing_pages      │    │         orders           │  ← commande
            │  product_id, payment_    │◀───│ customer_email,          │
            │  account_ids[], cod_     │    │ shipping_address,        │
            │  enabled, price_         │    │ subtotal/shipping/tax/   │
            │  overrides              │    │ discount/total,          │
            └──────────────────────────┘    │ payment_status,          │
                                            │ fulfillment_status,      │
                                            │ landing_id,              │
                                            │ click_id,  ← attribution │
                                            │ campaign_id,             │
                                            │ source_id,               │
                                            │ payment_account_id,      │
                                            │ payment_reference        │
                                            └────────────┬─────────────┘
                                                         │ 1..N
                                ┌────────────────────────┼────────────────────────┐
                                ▼                        ▼                        ▼
                  ┌─────────────────────┐  ┌─────────────────────┐  ┌─────────────────────┐
                  │     order_items     │  │ order_transactions  │  │  product_reviews    │
                  │ (variant snapshot:  │  │ (kind: authorize/   │  │ (rating 1-5, body,  │
                  │  product_name, sku, │  │  capture/refund,    │  │  status pending/    │
                  │  unit_price,        │  │  amount, provider,  │  │  approved/rejected, │
                  │  quantity)          │  │  provider_reference)│  │  verified_purchase) │
                  └─────────────────────┘  └─────────────────────┘  └─────────────────────┘

         ┌────────────────────┐   ┌────────────────────┐   ┌────────────────────┐
         │  payment_accounts  │   │  shipping_zones    │   │  discount_codes    │
         │ (Stripe, PayPal,   │   │ (country_codes[],  │   │ (percent / fixed / │
         │  COD ; api_secret, │   │  is_rest_of_world) │   │  free_shipping ;   │
         │  oauth_access_     │   │     │              │   │  product_id NULL = │
         │  token,            │   │     │ 1..N         │   │  global ; max_uses)│
         │  webhook_secret ;  │   │     ▼              │   └────────────────────┘
         │  mode live/test ;  │   │  shipping_rates    │
         │  oauth_user_id)    │   │  (name, price,     │
         └────────────────────┘   │  currency,         │
                                  │  min/max_order_    │
                                  │  amount)           │
                                  └────────────────────┘

Les tables clé en une phrase

  • native_products — la fiche produit (nom, slug, description, type physical/digital, currency, status draft/active, catégorie, images, tax_rate, shipping_cost, allowed_domains). Une seule ligne par produit, même quand il a 18 variantes.
  • native_product_options — les axes de variante (Color, Size, Material). Maximum 3 par produit, identifiés par position 0/1/2. Le champ values est un tableau JSON des valeurs possibles.
  • native_product_variants — une ligne par combinaison cartésienne d'options. Porte le prix unitaire, le SKU, le stock (-1 = illimité), l'image de variante et le flag is_active.
  • orders — une ligne par commande. Capture l'email + adresse client, les totaux décomposés (subtotal/shipping/tax/discount/total), le statut paiement, le statut fulfillment, et surtout les colonnes d'attribution (landing_id, click_id, campaign_id, source_id) qui sont la raison d'être de tout ça.
  • order_items — une ligne par variante achetée. Snapshote product_name, variant_label, sku, unit_price au moment de l'INSERT pour que les commandes historiques survivent à un edit ou un soft-delete du produit.
  • order_transactions — ledger d'audit pour chaque événement de paiement (authorize, capture, refund, void). Conserve la raw_response complète du provider pour les disputes.
  • payment_accounts — les comptes Stripe / PayPal connectés. Multi-compte natif : tu peux router différentes landings sur différents comptes Stripe. Voir payment-accounts.md et stripe-connect.md.
  • shipping_zones + shipping_rates — regroupement de pays + N tarifs par zone. Une zone "rest-of-world" (unique en base) sert de catch-all.
  • discount_codes — codes promo percent / fixed / free_shipping, avec limites d'usage, dates de validité et min_order_amount.
  • product_reviews — étoiles + commentaires soumis publiquement sur /p/<slug>. Modération par défaut, badge verified_purchase calculé à l'insertion.

Flux d'une vente

  1. Visiteur clique sur ta source → /c/<slug> (Trackily) → cookie click_id.
  2. Redirection vers la landing → le visiteur voit le bloc checkout sous le formulaire (composé des boutons "Pay with Stripe", "Pay with PayPal", "Cash on delivery" selon ce que tu as activé).
  3. Il choisit sa variante, remplit son adresse, clique sur un bouton.
  4. POST /api/order → Trackily crée la ligne orders en payment_status='pending', snapshote le panier dans order_items, calcule le shipping via shipping_zones, applique le code promo, redirige vers Stripe Checkout / PayPal / page de confirmation COD.
  5. Webhook checkout.session.completed (ou validation manuelle pour COD) → payment_status='paid', paid_at=NOW(), stock décrémenté atomiquement.
  6. Tu fulfilles la commande depuis /admin#orders ou via MCP fulfill_native_order (tracking number + carrier).
  7. Si remboursement : refund_native_order → appel API Stripe/PayPal → payment_status='refunded' ou 'partial_refund', stock restocké.

À chaque étape, la colonne click_id reste collée à la commande — ton ROAS par campagne est calculé sans dépendre d'un pixel tiers.

Ce qui est dans cette section

Onze pages couvrent l'ensemble du module. Aborde-les dans cet ordre si tu démarres :

  1. products.md — créer ton premier produit (UI + MCP create_product / list_native_products / get_native_product_detail).
  2. variants.md — décliner ce produit en variantes (couleur, taille, prix par variante).
  3. categories.md — organiser le catalogue et les filter pills de la page /catalog.
  4. payment-accounts.md — connecter Stripe et PayPal (manuel).
  5. stripe-connect.md — la nouvelle voie OAuth en un clic (Phase 18).
  6. shipping.md — zones géographiques et tarifs de livraison.
  7. discounts.md — codes promo et leurs règles d'application.
  8. reviews.md — collecter et modérer les avis clients.
  9. orders.md — le cœur du module : cycle de vie des commandes.
  10. fulfillment.md — expédier les commandes et joindre le tracking.
  11. refunds.md — rembourser partiellement ou totalement.

Pré-requis avant de vendre

Pour ouvrir le premier checkout fonctionnel, tu as besoin au minimum de :

  • 1 produit en status='active' avec au moins 1 variante is_active=true
  • 1 zone de shipping qui couvre les pays que tu vises (ou la zone rest-of-world)
  • 1 compte de paiement connecté OU cod_enabled=true sur la landing (cash on delivery)
  • 1 landing avec son product_id rempli OU la page /p/<slug> du produit

Le reste (reviews, discounts, emails post-purchase) est optionnel mais fortement recommandé pour la conversion.

Erreurs courantes au démarrage

  • "Pourquoi le bloc checkout n'apparaît pas ?" — la landing n'a pas son product_id rempli, ou le produit est en status='draft', ou aucun payment_account_ids n'est attaché ET cod_enabled=false. Va dans le détail de la landing puis dans l'onglet Commerce.
  • "L'order reste 'pending' alors que Stripe a encaissé" — le webhook n'a pas tapé sur ton instance. Vérifie l'URL de webhook Stripe dans ton compte Connect (doit pointer sur /webhook/stripe/native-pending). Avec la Phase 18 OAuth, le webhook est créé automatiquement.
  • "Erreur 'A rest-of-world zone already exists'" — il ne peut y avoir qu'une seule zone is_rest_of_world=true system-wide. Edit l'existante au lieu d'en créer une deuxième.
  • "Mon SKU custom ne se sauvegarde pas" — vérifie que la variante a bien is_active=true. Une variante inactive est ignorée par toute la pipeline (catalog, checkout, stock).
  • "Le visiteur voit le checkout d'un produit cloné par un concurrent" — utilise allowed_domains (JSONB array sur native_products) pour whitelister les hosts autorisés. Voir products.md.

Voir aussi