Ce chapitre termine notre parcours Ibexa Commerce par la zone la plus sensible d’une boutique : le passage du panier à la commande. L’objectif n’est pas de fabriquer un faux checkout dans Next.js. Nous conservons Ibexa comme source de vérité et nous connectons les interfaces autour de lui.
Le résultat recherché
Le projet propose deux expériences qui utilisent le même backend :
- le storefront classique Ibexa/Twig ;
- le storefront headless Next.js sur le port
3001.
Dans les deux cas, Ibexa possède le panier, calcule les prix, la TVA et les remises, crée le paiement et enregistre la commande. Next.js ne recalcule jamais un montant sensible.
Le checkout natif est accessible depuis le panier Next.js avec Continuer vers le checkout Ibexa. Il gère successivement l’adresse, la livraison, le moyen de paiement, le récapitulatif et la confirmation.
Dans le mapper du panier, l’URL est construite à partir de l’identifiant renvoyé par Ibexa :
checkoutUrl: `${IBEXA_ORIGIN}/checkout/init/${encodeURIComponent(identifier)}`,
Le composant reste volontairement très simple :
<a href={cart.checkoutUrl}>Continuer vers le checkout Ibexa</a>
<small>Le checkout sécurisé reste géré par le backend Ibexa.</small>
Ce lien matérialise une frontière importante : Next.js affiche le panier, tandis qu’Ibexa reprend la main pour l’adresse, la livraison, le paiement et la création de commande.
Architecture et rôle des connecteurs
Le flux est volontairement simple :
Storefront Twig ─┐
├─> Ibexa Cart / Checkout / Payment / Order
Next.js + REST ──┘ │
└─> Connector Payum ─> Stripe sandbox
Cette séparation est le point central du projet : Next.js présente le catalogue et le panier, mais Ibexa reste propriétaire du checkout et de la commande. Le navigateur quitte donc temporairement le front headless pour suivre le tunnel sécurisé d’Ibexa, puis retrouve la commande depuis l’espace client.
Ibexa Checkout
Le composant transforme un panier en parcours de commande. Il orchestre les étapes et transmet le total déjà calculé par Commerce.
Ibexa Payment
Il conserve le paiement et son état métier. Un paiement pending, paid, failed ou cancelled doit rester traçable indépendamment de l’écran présenté au client.
Ibexa Connector Payum
Le connecteur adapte le modèle de paiement Ibexa à une passerelle Payum. Il prépare le montant, la devise, la référence de commande et le retour vers le checkout.
Payum Stripe
La passerelle stripe_test utilise uniquement des clés Stripe de test. Les clés secrètes restent dans Symfony, jamais dans Next.js ou dans Git.
Le paquet compatible avec ce projet est installé par Composer :
composer require payum/stripe:^1.7
La passerelle est déclarée dans config/packages/payum.yaml :
payum:
gateways:
stripe_test:
factory: stripe_checkout
publishable_key: '%env(STRIPE_PUBLISHABLE_KEY)%'
secret_key: '%env(STRIPE_SECRET_KEY)%'
Les clés sont lues depuis l’environnement Symfony. Le secret n’est donc ni écrit dans le dépôt ni transmis à Next.js.
Attention : cette version repose sur l’intégration Stripe Charges historique. Elle sert au laboratoire Ibexa/Payum, mais une mise en production exige de valider SCA/3DS, les webhooks signés, l’idempotence, les remboursements et la version d’API supportée avec un connecteur maintenu.
Activer Stripe en sandbox
Le dépôt contient des valeurs factices. Elles permettent au conteneur Symfony de compiler sans exposer de secret, mais elles ne peuvent effectuer aucun paiement.
Créer .env.local :
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
Puis vider le cache et créer la méthode de paiement :
php bin/console cache:clear
php bin/console ibexa:migrations:migrate --file=20_stripe_test_payment_method.yaml
La configuration Payum déclare une passerelle stripe_test. La méthode de paiement créée par migration référence cette passerelle : c’est ce lien qui fait apparaître Stripe card — sandbox dans l’étape de sélection du paiement.
Voici la partie essentielle de la migration :
- type: payment_method
mode: create
paymentType: stripe_test
name:
- languageCode: eng-GB
value: 'Stripe card — sandbox'
identifier: stripe_test
enabled: true
paymentType doit correspondre au nom de la passerelle Payum. identifier est l’identifiant métier de la méthode visible dans Ibexa.
La migration est séparée des données de démonstration par défaut. Ainsi, une installation sans compte Stripe continue d’utiliser le paiement offline.
Personnaliser le checkout sans toucher à vendor
Le layout est surchargé dans :
templates/themes/storefront/checkout/layout.html.twig
public/css/commerce-lab-checkout.css
Le template étend celui du bundle Ibexa, ajoute l’identité Commerce Lab et conserve les formulaires, jetons CSRF et transitions fournis par le moteur. Cette approche résiste mieux aux mises à jour qu’une modification directe de vendor/.
Le checkout rappelle aussi un principe essentiel : les prix, la TVA, les promotions et la commande sont calculés par le backend.
Commandes dans le FO classique
Ibexa Storefront fournit déjà les routes sécurisées :
/user/order
/user/order/{orderIdentifier}
Le client connecté ne voit que les commandes autorisées par Ibexa. Le BO reste l’outil de l’équipe e-commerce pour confirmer la commande, suivre le paiement et faire évoluer l’expédition.
Commandes dans Next.js
Next.js ne se connecte pas directement à la base. Il transmet la session Ibexa côté serveur puis appelle l’API REST :
GET /api/ibexa/v2/orders/orders
Les routes locales sont :
/account
/account/orders/{identifier}
/api/storefront/orders
/api/storefront/orders/{identifier}
La route de détail recharge d’abord les commandes autorisées pour la session puis recherche l’identifiant demandé. Un identifiant arbitraire ne suffit donc pas pour lire la commande d’un autre client.
Facture ou reçu ?
La page de détail peut être imprimée ou enregistrée en PDF depuis le navigateur. Elle est explicitement nommée reçu de commande — démonstration.
Une facture fiscale réelle demanderait au minimum :
- une numérotation unique et immuable ;
- les identités légales du vendeur et de l’acheteur ;
- le détail des bases et taux de TVA ;
- les dates réglementaires ;
- les règles d’avoir et de conservation ;
- souvent une intégration avec le système comptable.
Il serait dangereux de présenter une simple vue HTML comme une facture légale. Le prochain connecteur de production pourrait transmettre la commande payée à un ERP ou à un service de facturation, puis rattacher le PDF final à la commande Ibexa.
Scénario de test
- Ouvrir
http://127.0.0.1:3001. - Ajouter un produit au panier.
- Vérifier que prix, taxe et remise viennent d’Ibexa.
- Cliquer sur Continuer vers le checkout Ibexa.
- Compléter l’adresse et choisir livraison et paiement.
- En sandbox seulement, utiliser une carte de test Stripe.
- Vérifier la commande dans
/user/ordercôté Twig. - Vérifier la même commande dans
/accountcôté Next.js. - Ouvrir son détail et tester l’impression du reçu.
Preuve du paiement de bout en bout
La capture suivante a été anonymisée, mais elle provient du test réellement exécuté. Elle rassemble les contrôles importants :
- la commande existe dans l’espace client Ibexa ;
- la méthode utilisée est Stripe card — sandbox ;
- le paiement est marqué Paid ;
- le total de 63,25 € correspond au montant présenté par Stripe ;
- la remise, la TVA, la livraison et les lignes produit restent détaillées par Ibexa.
Le paiement n’est donc pas simulé dans Next.js. Stripe tokenise la carte de test, Payum traite le retour, puis Ibexa met à jour son objet paiement et conserve la commande.
Les incidents rencontrés — et leur cause
« No php-http message factories found »
Payum 1.7 cherche encore l’ancienne abstraction php-http/message-factory. Sans elle, le checkout échoue au moment de construire la passerelle. Le correctif utilisé dans ce laboratoire est :
composer require php-http/message-factory:^1.1
Ce paquet est un pont de compatibilité. Pour un nouveau connecteur, PSR-17 doit être préféré.
Stripe refuse la tokenisation par clé publique
L’intégration historique utilise un formulaire Stripe ancien. Dans l’environnement de test Stripe, il a fallu autoriser la collecte par clé publique sans Stripe Elements. Cette option permet de terminer le laboratoire, mais elle ne transforme pas cette intégration legacy en architecture de production.
La commande reste « Pending » alors que le paiement est « Paid »
Ce sont deux états métier différents. Paid confirme le paiement. La commande peut rester Pending tant que le workflow de traitement n’a pas été confirmé, et l’expédition possède encore son propre cycle de vie. Cette séparation évite de confondre encaissement, préparation et livraison.
Tests indispensables avant une production
- succès, refus et annulation du paiement ;
- retour navigateur interrompu ;
- callback ou webhook rejoué plusieurs fois ;
- absence de duplication de commande ;
- cohérence entre montant Ibexa et montant transmis ;
- contrôle d’accès aux commandes ;
- remboursement total et partiel ;
- expédition après confirmation du paiement ;
- expiration des sessions et protection CSRF.
Relancer le projet plus tard
symfony server:start --port=8003 --no-tls -d
cd frontend-next
npm install
npm run dev -- --port 3001
Points d’entrée :
- Ibexa :
http://127.0.0.1:8003 - checkout natif : lancé depuis le panier ;
- commandes FO classique :
http://127.0.0.1:8003/user/order - Next.js :
http://127.0.0.1:3001 - compte Next.js :
http://127.0.0.1:3001/account
Ce que couvre réellement ce REX
Le projet possède désormais catalogue, variantes, prix, stock, remises catalogue, codes panier, segmentation VIP, panier REST, authentification client, checkout natif, commandes dans deux fronts et reçu imprimable.
Stripe a été validé avec de vraies clés sandbox et la carte de test 4242 4242 4242 4242. Le parcours observé est complet : sélection de la méthode Stripe, tokenisation côté Stripe, retour Payum vers Ibexa, création de la commande et paiement enregistré avec le statut Paid.
Le test a confirmé que le montant de 63,25 € affiché par Stripe correspondait exactement au total TTC calculé par Ibexa. La même commande est ensuite apparue dans le FO classique avec la méthode Stripe card — sandbox et le statut Paid.
La dépendance de compatibilité permet à cette ancienne version de Payum de découvrir la fabrique HTTP PSR-7 fournie par Nyholm. Elle doit être considérée comme un pont de laboratoire, pas comme le choix d’une nouvelle intégration de production.
Stripe demande également d’autoriser, dans l’environnement de test, la collecte par clé publique sans ses composants modernes. Cette contrainte confirme le caractère legacy de payum/stripe 1.7. Une future mise en production devrait privilégier Stripe Checkout Sessions ou Elements et un webhook signé.
Cette frontière entre démonstration validée et architecture de production reste aussi importante que le code lui-même.

