Ibexa Commerce 5 en headless : catalogue, panier et promotions avec l’API REST — REX

Ibexa Commerce 5 en headless : construire un catalogue et un panier avec l’API REST

Après avoir migré notre boutique de formation vers Ibexa Commerce 5.0.11, nous voulions répondre à une question très concrète : que signifie réellement “faire du headless” avec Ibexa Commerce ?

Plutôt que d’en rester à une définition abstraite, nous avons construit un petit storefront en HTML, CSS et JavaScript natifs. Il affiche les produits, charge les variantes, crée un panier, recalcule les quantités, applique un code promotionnel et passe ensuite le relais au checkout natif d’Ibexa.

Le résultat est une architecture hybride : catalogue et panier en REST/JSON, puis checkout Symfony sécurisé. C’est une façon pragmatique d’apprendre le headless sans réécrire immédiatement toute la chaîne de commande.

Le code de la démonstration est disponible sur la branche feature/ibexa-5-headless-commerce.

Headless : une définition simple

Dans une application Symfony classique, le serveur prépare les données, Twig construit le HTML, puis le navigateur affiche cette page.

Ibexa Commerce → contrôleur Symfony → template Twig → HTML → navigateur

Dans une architecture headless, le backend ne décide plus de la présentation finale. Il expose des données structurées, généralement en JSON. Une autre application consomme ces données et construit l’interface.

Ibexa Commerce → API REST → JSON → frontend JavaScript → interface

Le mot “headless” ne signifie donc pas “sans interface”. Il signifie que la tête — la couche de présentation — est découplée du moteur de contenu et de commerce.

Responsabilité Mode Symfony/Twig Mode headless de notre REX
Produits, prix, stocks Ibexa Commerce Ibexa Commerce
Promotions et calculs Ibexa Commerce Ibexa Commerce
Production du HTML Twig côté serveur JavaScript côté navigateur
Transport des données variables du template API REST et JSON
Checkout Symfony/Twig Symfony/Twig après passage de relais

Le point essentiel est là : nous ne déplaçons pas les règles métier dans JavaScript. Ibexa reste la source de vérité pour le catalogue, les prix, les stocks, les remises et les totaux.

Ce que nous avons construit

La démonstration est disponible localement à l’adresse :

http://127.0.0.1:8003/headless-demo/

Elle est volontairement écrite sans framework frontend. Utiliser du JavaScript natif permet de voir exactement chaque requête et chaque transformation, sans masquer le fonctionnement derrière React, Vue ou Next.js.

Storefront headless alimenté par l’API REST d’Ibexa Commerce

La page affiche quatre produits réels du catalogue de formation :

  • une formation Ibexa Commerce ;
  • un carnet pour développeur ;
  • un poster d’architecture ;
  • un T-shirt parent décliné en six variantes couleur × taille.

Étape 1 — Interroger un catalogue

Nous filtrons le catalogue demo_catalog avec l’endpoint suivant :

POST /api/ibexa/v2/product/catalog/catalogs/demo_catalog/products/view

Pourquoi un POST pour une opération de lecture ? Parce que la recherche transmet un objet structuré contenant pagination, filtres et tri :

{
  "ViewInput": {
    "identifier": "headless_catalog",
    "ProductQuery": {
      "offset": 0,
      "limit": 12,
      "Filter": {
        "ProductAvailabilityCriterion": true
      },
      "SortClauses": {
        "ProductName": "ascending"
      }
    }
  }
}

Les types de médias Ibexa sont également importants :

Accept: application/vnd.ibexa.api.ProductView+json
Content-Type: application/vnd.ibexa.api.ProductViewInput+json

Le frontend reçoit une liste JSON puis fabrique les cartes produit. Aucun produit n’est inscrit en dur dans le HTML initial.

Étape 2 — Charger une fiche, son prix et son stock

Lorsqu’un visiteur ouvre une carte, le navigateur charge la ressource complète :

GET /api/ibexa/v2/product/catalog/products/{code}

La réponse regroupe plusieurs familles de données :

  • Content pour le nom, la description, l’image et les champs éditoriaux ;
  • ProductType pour le type de produit ;
  • Price pour le prix et la devise ;
  • Availability pour la disponibilité et le stock ;
  • code pour l’identifiant commercial stable.

Cette réponse permet aussi de clarifier un point souvent mal compris : le champ ibexa_product_specification transforme un contenu en produit et porte notamment son code, mais le prix et le stock sont gérés séparément par les composants Commerce. L’API rassemble ces données au moment de restituer le produit.

Étape 3 — Distinguer produit parent et variante achetable

Le T-shirt est un produit parent. Il porte les informations communes, mais ce n’est pas le code parent qui est placé dans le panier.

Ses combinaisons sont chargées avec :

POST /api/ibexa/v2/product/catalog/product_variants/view/IBEXA-TSHIRT

Chaque variante possède :

  • un code distinct, par exemple IBEXA-TSHIRT-BLACK-M ;
  • une couleur et une taille ;
  • son propre prix ;
  • sa propre disponibilité.

Quand l’utilisateur change la sélection, le frontend recharge la variante choisie. Il ne devine pas son prix et ne calcule pas son stock localement.

Étape 4 — Créer un vrai panier REST

Le panier est créé seulement au premier ajout :

POST /api/ibexa/v2/cart
{
  "CartCreate": {
    "name": "Commerce Lab headless",
    "currencyCode": "EUR",
    "context": {
      "source": "headless-demo"
    }
  }
}

Ibexa renvoie un UUID. Notre navigateur conserve uniquement cet identifiant dans localStorage. Le contenu commercial du panier reste stocké côté Ibexa.

Pour ajouter la variante noire en taille M :

POST /api/ibexa/v2/cart/{cartIdentifier}/entry
{
  "CartEntryAdd": {
    "quantity": 1,
    "Product": {
      "code": "IBEXA-TSHIRT-BLACK-M"
    },
    "context": {
      "source": "headless-demo"
    }
  }
}

Les quantités et suppressions passent également par l’API :

PATCH  /api/ibexa/v2/cart/{cartIdentifier}/entry/{entryIdentifier}
DELETE /api/ibexa/v2/cart/{cartIdentifier}/entry/{entryIdentifier}

Étape 5 — Ne jamais recalculer les totaux dans le frontend

Après chaque modification, nous rechargeons le résumé :

GET /api/ibexa/v2/cart/{cartIdentifier}/summary

Il contient les lignes, les quantités, les prix unitaires, les sous-totaux, les remises, les montants HT et TTC ainsi que la TVA par catégorie.

Cette règle est fondamentale : le navigateur affiche le résultat d’Ibexa, il ne réimplémente pas le moteur de prix. Le calcul dépend potentiellement du catalogue, du client connecté, du groupe client, d’une promotion, d’un code et du contexte de vente.

Étape 6 — Appliquer une promotion au panier

Notre laboratoire possède un code de formation IBEXA15. Il est envoyé à :

POST /api/ibexa/v2/discounts_codes/{cartIdentifier}/apply
{
  "DiscountCode": {
    "code": "IBEXA15"
  }
}

Le frontend recharge ensuite le résumé du panier. Sur un T-shirt à 29,90 € HT, Ibexa retourne une remise de 2,99 € dans le contexte actuel, puis un total de 26,91 € HT et 32,29 € TTC.

Panier headless avec code promotionnel calculé par Ibexa

Cette capture illustre bien la séparation des responsabilités : le formulaire appartient au frontend, mais l’acceptation du code et le montant de la remise viennent d’Ibexa Commerce.

Étape 7 — Comprendre la protection CSRF

Notre premier appel catalogue a renvoyé :

Missing or invalid CSRF token

Ce n’était pas un bug de l’API. Le navigateur possédait une session Ibexa active et la recherche catalogue utilise POST. Ibexa exige donc un jeton CSRF sur les méthodes non sûres.

Nous avons ajouté un petit endpoint Symfony qui génère le jeton avec l’intention REST :

#[Route('/headless-demo/csrf-token', methods: ['GET'])]
public function csrfToken(CsrfTokenManagerInterface $csrfTokenManager): JsonResponse
{
    return $this->json([
        'token' => $csrfTokenManager->getToken('rest')->getValue(),
    ]);
}

Le client le transmet ensuite dans l’en-tête :

X-CSRF-Token: <jeton lié à la session>

Nous avons volontairement conservé la sécurité active. Désactiver CSRF pour faire fonctionner une démonstration aurait masqué un élément essentiel d’une intégration réelle.

Étape 8 — Passer au checkout natif

Dans cette installation, les ressources catalogue et panier sont disponibles via REST. Le parcours complet adresse, livraison, paiement et création de commande reste fourni par le storefront Symfony.

Le bouton final redirige donc vers :

/checkout/init/{cartIdentifier}

Nous obtenons une architecture hybride :

Catalogue headless
    → fiche et variantes REST
    → panier et promotions REST
    → checkout natif Ibexa
    → commande

Ce choix permet de personnaliser fortement la découverte produit sans réécrire immédiatement la partie la plus sensible du tunnel de vente.

Quand cette architecture est-elle utile ?

Le headless devient intéressant lorsqu’un même moteur Commerce doit alimenter plusieurs expériences :

  • un site React, Vue ou Next.js ;
  • une application mobile ;
  • une borne en magasin ;
  • un portail B2B ;
  • une interface embarquée chez un partenaire.

Il apporte de la liberté frontend, mais ajoute aussi des responsabilités : authentification, CORS, cache, gestion des erreurs, observabilité, SEO et cohérence du panier entre les canaux.

Ce que ce REX prouve — et ce qu’il ne prétend pas faire

Nous avons validé avec Ibexa Commerce 5.0.11 :

  • le filtrage d’un catalogue ;
  • le chargement des produits et des variantes ;
  • la restitution du prix et du stock ;
  • la création et la persistance d’un panier ;
  • la modification et la suppression des lignes ;
  • l’application d’un code promotionnel ;
  • le calcul HT, TVA et TTC ;
  • le passage vers le checkout ;
  • la protection CSRF d’une session REST.

Cette démonstration n’est pas un storefront de production complet. Un projet réellement découplé devrait encore traiter l’authentification, CORS, les performances, le cache, l’accessibilité, le SEO, la gestion avancée des erreurs et éventuellement un checkout entièrement headless.

Conclusion

Le headless n’est pas une suppression de Symfony ou d’Ibexa. C’est une nouvelle répartition des responsabilités.

Ibexa Commerce continue de gérer les données et les règles métier. L’API REST les expose. Le frontend choisit ensuite comment construire l’expérience visible. Dans notre REX, le catalogue et le panier sont découplés, tandis que le checkout natif sécurise la fin du parcours.

Cette première implémentation constitue une base concrète pour aller plus loin : authentification REST, frontend Next.js, cache applicatif, recherche avancée et stratégie omnicanale.