[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"theme-db":3,"$fKnz2vuX4bZz1LbUTiuFsvSZ3e07l5_5fqNYp4Tzdhi8":22,"$ftu430nPu2s4C4OAkSYd_LUHzUFrtwiiT3S5hSQABKp4":103,"header-db":141,"$fNDTORQaw5TsrvcPvehY_DTpGMQ7sRWwFITd04nJcDEE":155,"footer-db":170,"megamenu":186},{"theme":4},{"colors":5,"typography":13,"ui":17,"defaultColorMode":21},{"primary":6,"secondary":7,"background":8,"foreground":9,"muted":10,"headerBg":11,"footerBg":12,"topBarBg":9,"topBarText":11},"#4F46E5","#0D9488","#F9FAFB","#111827","#6B7280","#ffffff","#020617",{"fontFamily":14,"fontUrl":15,"baseFontSize":16},"Inter, system-ui, sans-serif","https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=Inter:wght@400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,700;0,800;0,900;1,400;1,700&display=swap","16px",{"borderRadius":18,"contentWidth":19,"shadow":20},"lg","7xl",true,"light",{"columns":23},[24,40,70,91],{"title":25,"links":26},"Plateforme",[27,31,34,37],{"label":28,"href":29,"external":30},"Offre Starter (2 500 €)","\u002Foffre-starter",false,{"label":32,"href":33,"external":30},"Devenir Ambassadeur","\u002Fambassadeur",{"label":35,"href":36,"external":30},"Modules PrestaShop","\u002Fmodules",{"label":38,"href":39,"external":20},"CodeMyShop.com","https:\u002F\u002Fcodemyshop.com",{"title":41,"links":42},"Le Synedre",[43,46,49,52,55,58,61,64,67],{"label":44,"href":45,"external":30},"L'histoire","\u002Fsynedre",{"label":47,"href":48,"external":30},"Constitution","\u002Fsynedre\u002Fconstitution",{"label":50,"href":51,"external":30},"L'équipe","\u002Fequipe",{"label":53,"href":54,"external":30},"Le réacteur en direct","\u002Freacteur",{"label":56,"href":57,"external":30},"Le Drill (entraînement)","\u002Fdrill",{"label":59,"href":60,"external":30},"Protocole de réunion","\u002Fsynedre\u002Freunion",{"label":62,"href":63,"external":30},"Les agents IA","\u002Fagents-ia",{"label":65,"href":66,"external":30},"La Conduite","\u002Fsynedre\u002Fconduite",{"label":68,"href":69,"external":30},"Charte plateforme","\u002Fsynedre\u002Fcharte",{"title":71,"links":72},"Ressources",[73,76,79,82,85,88],{"label":74,"href":75,"external":30},"Blog","\u002Fblog",{"label":77,"href":78,"external":30},"Academy","\u002Facademy",{"label":80,"href":81,"external":30},"Dictionnaire","\u002Fdictionnaire",{"label":83,"href":84,"external":30},"Expertise PrestaShop","\u002Fexpertise",{"label":86,"href":87,"external":30},"Flywheel","\u002Fflywheel",{"label":89,"href":90,"external":30},"Manifeste","\u002Fmanifeste",{"title":92,"links":93},"À propos",[94,97,100],{"label":95,"href":96,"external":30},"Alexandre Carette","\u002Fa-propos",{"label":98,"href":99,"external":30},"Dossier de presse","\u002Fpresse",{"label":101,"href":102,"external":30},"Contact","\u002Fcontact",{"title":104,"slug":105,"metaDescription":106,"category":107,"tags":108,"difficulty":115,"psVersions":116,"content":119,"faq":120,"tldr":136,"readingTime":137,"generatedAt":138,"publishDate":138,"relatedArticles":139,"sourceCategory":140},"Ajouter un loader lors de la soumission de commande PrestaShop","ajouter-loader-soumission-commande-prestashop","Comment ajouter un spinner de chargement au clic sur le bouton de commande PrestaShop pour éviter les doubles clics et améliorer l'UX du tunnel d'achat.","developpement",[109,110,111,112,113,114],"checkout","javascript","ux","tunnel-commande","loader","smarty","intermediaire",[117,118],"1.7","8.x","\u003Ch2>Pourquoi ajouter un loader au tunnel de commande\u003C\u002Fh2>\n\u003Cp>Lorsqu'un client clique sur le bouton \"Commander\" à l'étape finale du checkout PrestaShop, il se passe parfois plusieurs secondes avant la redirection vers la page de confirmation ou la passerelle de paiement. Sans retour visuel, le client peut :\u003C\u002Fp>\n\u003Cul>\n\u003Cli>**Double-cliquer** et générer deux commandes identiques\u003C\u002Fli>\n\u003Cli>**Quitter la page** en pensant que rien ne se passe\u003C\u002Fli>\n\u003Cli>**Perdre confiance** dans la fiabilité de la boutique\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Ajouter un spinner (loader) au moment de la soumission est une amélioration UX simple mais redoutablement efficace pour le taux de conversion.\u003C\u002Fp>\n\u003Ch2>Étape 1 : Identifier le bon fichier template\u003C\u002Fh2>\n\u003Cp>Dans PrestaShop 1.7 et 8.x, le tunnel de commande est géré par le template :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-\">\nthemes\u002Fvotre-theme\u002Ftemplates\u002Fcheckout\u002Fcheckout.tpl\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Le bouton de validation finale se trouve plus précisément dans :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-\">\nthemes\u002Fvotre-theme\u002Ftemplates\u002Fcheckout\u002F_partials\u002Fsteps\u002Fpayment.tpl\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>C'est le formulaire contenant le bouton \u003Ccode>#payment-confirmation button\u003C\u002Fcode> qui déclenche la soumission.\u003C\u002Fp>\n\u003Ch2>Étape 2 : Créer le CSS du loader\u003C\u002Fh2>\n\u003Cp>Ajoutez ces styles dans votre fichier CSS personnalisé ou dans \u003Ccode>themes\u002Fvotre-theme\u002Fassets\u002Fcss\u002Fcustom.css\u003C\u002Fcode> :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-css\">\n\u002F* Overlay de chargement sur le bouton de commande *\u002F\n.checkout-loader-overlay {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background: rgba(0, 0, 0, 0.4);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  z-index: 9999;\n}\n\n.checkout-loader-overlay .spinner {\n  width: 50px;\n  height: 50px;\n  border: 4px solid rgba(255, 255, 255, 0.3);\n  border-top-color: #fff;\n  border-radius: 50%;\n  animation: spin 0.8s linear infinite;\n}\n\n.checkout-loader-overlay p {\n  color: #fff;\n  margin-top: 15px;\n  font-size: 16px;\n}\n\n@keyframes spin {\n  to { transform: rotate(360deg); }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Étape 3 : Ajouter le JavaScript\u003C\u002Fh2>\n\u003Cp>Voici le point crucial. Dans PrestaShop 1.7\u002F8.x, les templates Smarty nécessitent d'encadrer le JavaScript natif avec les balises \u003Ccode>{literal}\u003C\u002Fcode> pour éviter que Smarty n'interprète les accolades comme des variables de template.\u003C\u002Fp>\n\u003Cp>Ajoutez ce script \u003Cstrong>juste avant la balise \u003Ccode>\u003C\u002Fbody>\u003C\u002Fcode>\u003C\u002Fstrong> dans votre template \u003Ccode>checkout.tpl\u003C\u002Fcode> ou via un module :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-smarty\">\n{literal}\n&lt;script&gt;\n  document.addEventListener('DOMContentLoaded', function() {\n    var paymentForm = document.querySelector('#payment-confirmation form');\n    if (!paymentForm) return;\n\n    var submitted = false;\n\n    paymentForm.addEventListener('submit', function(e) {\n      \u002F\u002F Empêcher le double-clic\n      if (submitted) {\n        e.preventDefault();\n        return false;\n      }\n      submitted = true;\n\n      \u002F\u002F Créer l'overlay loader\n      var overlay = document.createElement('div');\n      overlay.className = 'checkout-loader-overlay';\n      overlay.innerHTML = '&lt;div style=\"text-align:center;\"&gt;&lt;div class=\"spinner\"&gt;&lt;\u002Fdiv&gt;&lt;p&gt;Traitement de votre commande en cours...&lt;\u002Fp&gt;&lt;\u002Fdiv&gt;';\n      document.body.appendChild(overlay);\n\n      \u002F\u002F Désactiver le bouton visuellement\n      var btn = paymentForm.querySelector('button[type=\"submit\"], input[type=\"submit\"]');\n      if (btn) {\n        btn.disabled = true;\n        btn.style.opacity = '0.6';\n      }\n    });\n  });\n&lt;\u002Fscript&gt;\n{\u002Fliteral}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Approche alternative via un module (recommandée en 8.x)\u003C\u002Fh3>\n\u003Cp>En PrestaShop 8.x, la bonne pratique est d'injecter le JavaScript via un module plutôt que de modifier le template directement. Utilisez le hook \u003Ccode>actionFrontControllerSetMedia\u003C\u002Fcode> :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\npublic function hookActionFrontControllerSetMedia($params)\n{\n    if ($this-&gt;context-&gt;controller instanceof OrderController) {\n        $this-&gt;context-&gt;controller-&gt;registerJavascript(\n            'module-checkout-loader',\n            'modules\u002F' . $this-&gt;name . '\u002Fviews\u002Fjs\u002Fcheckout-loader.js',\n            ['position' =&gt; 'bottom', 'priority' =&gt; 200]\n        );\n        $this-&gt;context-&gt;controller-&gt;registerStylesheet(\n            'module-checkout-loader-css',\n            'modules\u002F' . $this-&gt;name . '\u002Fviews\u002Fcss\u002Fcheckout-loader.css',\n            ['media' =&gt; 'all', 'priority' =&gt; 200]\n        );\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Dans ce cas, le fichier \u003Ccode>checkout-loader.js\u003C\u002Fcode> contient le même code sans les balises \u003Ccode>{literal}\u003C\u002Fcode> :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">\ndocument.addEventListener('DOMContentLoaded', function() {\n  var paymentForm = document.querySelector('#payment-confirmation form');\n  if (!paymentForm) return;\n\n  var submitted = false;\n\n  paymentForm.addEventListener('submit', function(e) {\n    if (submitted) {\n      e.preventDefault();\n      return false;\n    }\n    submitted = true;\n\n    var overlay = document.createElement('div');\n    overlay.className = 'checkout-loader-overlay';\n    overlay.innerHTML = '&lt;div style=\"text-align:center;\"&gt;&lt;div class=\"spinner\"&gt;&lt;\u002Fdiv&gt;&lt;p&gt;Traitement de votre commande en cours...&lt;\u002Fp&gt;&lt;\u002Fdiv&gt;';\n    document.body.appendChild(overlay);\n\n    var btn = paymentForm.querySelector('button[type=\"submit\"], input[type=\"submit\"]');\n    if (btn) {\n      btn.disabled = true;\n      btn.style.opacity = '0.6';\n    }\n  });\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Diagnostic : erreur 500 après modification\u003C\u002Fh2>\n\u003Cp>Si vous obtenez une erreur 500 après avoir ajouté votre JavaScript, voici la marche à suivre :\u003C\u002Fp>\n\u003Ch3>Activer le mode debug\u003C\u002Fh3>\n\u003Cp>Dans le fichier \u003Ccode>.env\u003C\u002Fcode> à la racine de PrestaShop 8.x :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-\">\nAPP_DEBUG=1\nAPP_ENV=dev\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Pour PrestaShop 1.7, modifiez \u003Ccode>\u002Fconfig\u002Fdefines.inc.php\u003C\u002Fcode> :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-php\">\ndefine('_PS_MODE_DEV_', true);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Consulter les logs serveur\u003C\u002Fh3>\n\u003Cp>Si le mode debug ne suffit pas, vérifiez les logs Apache ou Nginx :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">\n# Apache\ntail -f \u002Fvar\u002Flog\u002Fapache2\u002Ferror.log\n\n# Nginx\ntail -f \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Les causes les plus fréquentes d'erreur 500 lors de l'ajout de JavaScript dans un template Smarty sont :\u003C\u002Fp>\n\u003Col>\n\u003Cli>**Accolades non protégées** : Smarty interprète `{` et `}` comme des délimiteurs. Encadrez systématiquement votre JS avec `{literal}...{\u002Fliteral}`\u003C\u002Fli>\n\u003Cli>**Fichier corrompu à l'upload** : Comparez avec une installation vierge de la même version\u003C\u002Fli>\n\u003Cli>**Cache Smarty périmé** : Videz le dossier `var\u002Fcache\u002F` et recompilez\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Comparer avec une installation vierge\u003C\u002Fh3>\n\u003Cp>En cas de doute sur l'intégrité de vos fichiers, téléchargez la version correspondante de PrestaShop depuis le site officiel et comparez les fichiers du tunnel de commande :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">\ndiff -r votre-theme\u002Ftemplates\u002Fcheckout\u002F prestashop-vierge\u002Fthemes\u002Fclassic\u002Ftemplates\u002Fcheckout\u002F\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Cela permet d'identifier rapidement si un fichier a été modifié ou corrompu.\u003C\u002Fp>\n\u003Ch2>Bonnes pratiques UX pour le loader\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>**Message rassurant** : affichez un texte comme \"Traitement en cours, ne fermez pas cette page\"\u003C\u002Fli>\n\u003Cli>**Timeout de sécurité** : ajoutez un `setTimeout` de 30 secondes qui masque le loader et réactive le bouton si la page ne redirige pas\u003C\u002Fli>\n\u003Cli>**Accessibilité** : ajoutez un attribut `aria-live=\"polite\"` sur le message pour les lecteurs d'écran\u003C\u002Fli>\n\u003Cli>**Mobile** : testez que le loader s'affiche correctement sur les petits écrans\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode class=\"language-javascript\">\n\u002F\u002F Timeout de sécurité\nsetTimeout(function() {\n  var overlay = document.querySelector('.checkout-loader-overlay');\n  if (overlay) {\n    overlay.remove();\n    submitted = false;\n    if (btn) {\n      btn.disabled = false;\n      btn.style.opacity = '1';\n    }\n  }\n}, 30000);\n\u003C\u002Fcode>\u003C\u002Fpre>",[121,124,127,130,133],{"q":122,"a":123},"Pourquoi mon JavaScript ne fonctionne pas dans un template Smarty PrestaShop ?","Smarty interprète les accolades { } comme des délimiteurs de variables. Si votre code JavaScript contient des accolades (fonctions, objets, conditions), Smarty tente de les interpréter et génère une erreur. La solution est d'encadrer tout votre bloc JavaScript avec les balises {literal}...{\u002Fliteral}, qui indiquent à Smarty d'ignorer le contenu. En PrestaShop 8.x, la meilleure approche reste de charger un fichier .js externe via un module et le hook actionFrontControllerSetMedia.",{"q":125,"a":126},"Comment empêcher les doubles commandes causées par un double-clic sur le bouton de paiement ?","La technique consiste à utiliser un flag JavaScript (une variable booléenne) qui passe à true dès le premier clic. Lors des clics suivants, le formulaire est bloqué via e.preventDefault(). Combinez cela avec la désactivation visuelle du bouton (disabled + opacity réduite) et l'affichage d'un loader pour informer le client que sa commande est en cours de traitement. Côté serveur, PrestaShop dispose également d'un token CSRF qui protège contre les soumissions multiples.",{"q":128,"a":129},"Où placer un script JavaScript personnalisé dans le tunnel de commande PrestaShop ?","Placez votre script juste avant la balise fermante \u003C\u002Fbody> dans le template checkout.tpl de votre thème. Cette position garantit que le DOM est chargé avant l'exécution. Pour une solution plus propre et compatible avec les mises à jour, créez un module qui utilise le hook actionFrontControllerSetMedia pour enregistrer votre fichier JS avec registerJavascript(). Cela évite de modifier les fichiers du thème directement.",{"q":131,"a":132},"Que faire si j'obtiens une erreur 500 après avoir modifié un template de checkout PrestaShop ?","Activez d'abord le mode debug (APP_DEBUG=1 dans .env pour PS 8.x, ou _PS_MODE_DEV_ dans defines.inc.php pour PS 1.7) pour obtenir le message d'erreur exact. Si ce n'est pas suffisant, consultez les logs du serveur web (error.log d'Apache ou Nginx). Les causes les plus courantes sont des accolades JavaScript non protégées par {literal}, un fichier corrompu lors du transfert FTP, ou un cache Smarty obsolète. Comparez vos fichiers avec une installation vierge de la même version pour isoler le problème.",{"q":134,"a":135},"Le loader de commande est-il compatible avec tous les modules de paiement PrestaShop ?","Le loader basé sur l'événement submit du formulaire #payment-confirmation fonctionne avec la majorité des modules de paiement natifs et tiers qui utilisent le flux standard de PrestaShop. Cependant, certains modules de paiement (Stripe, PayPal) redirigent vers une iframe ou une popup externe avant la soumission du formulaire. Dans ce cas, adaptez le sélecteur JavaScript au bouton spécifique du module ou utilisez un MutationObserver pour détecter dynamiquement les changements dans le DOM du checkout.","Pour ajouter un loader au tunnel de commande PrestaShop, injectez un script JavaScript (protégé par {literal} en Smarty) qui affiche un overlay au submit du formulaire de paiement et désactive le bouton pour éviter les doubles commandes. En PrestaShop 8.x, privilégiez un module avec le hook actionFrontControllerSetMedia.",5,"2026-03-21T17:01:46.000Z",[],"PrestaShop pour les développeurs",{"header":142},{"logo":143,"topBar":148,"contactEmail":152,"features":153,"navBar":149},{"src":144,"alt":145,"text":95,"href":146,"class":147},"\u002Flogo-ac.svg","Alexandre Carette — Architecte E-commerce Souverain","\u002F","h-10 w-10",{"message":149,"showLanguages":30,"align":150,"languages":151},null,"left",[],"contact@alexandrecarette.fr",{"showSearch":30,"showWishlist":30,"showLogin":20,"showContact":30,"showCart":30,"stickyHeader":20,"headerLayout":154},"inline",{"academy":156,"blog":157,"expertise":169},[],[158,163,166],{"title":159,"url":160,"score":161,"type":162},"PrestaShop headless avec Nuxt 3 : pourquoi séparer back et front","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fprestashop-headless-nuxt-separation-front-back",1,"blog",{"title":164,"url":165,"score":161,"type":162},"PrestaShop headless : Nuxt 3, pas Next.js — le choix souverain","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fprestashop-headless-nuxt-nextjs-souverainete",{"title":167,"url":168,"score":161,"type":162},"Sylius rachète PrestaShop : ce que ça change pour vous","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fsylius-rachat-prestashop-headless-souverainete",[],{"footer":171},{"theme":172,"description":149,"hours":149,"logo":173,"contact":174,"social":175,"bottomBar":185},"dark",{"src":144,"href":146,"alt":95},{"email":149,"phone":149,"address":149,"cta":149},[176,179,182],{"platform":177,"href":178,"label":177},"linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falexandre-carette\u002F",{"platform":180,"href":181,"label":180},"malt","https:\u002F\u002Fwww.malt.fr\u002Fprofile\u002Falexandrecarette",{"platform":183,"href":184,"label":183},"github","https:\u002F\u002Fgithub.com\u002Fprest4cafe",{"copyright":149},{"items":187},[188,196,201,207,215,223,228,234],{"id":189,"type":190,"label":191,"href":84,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":30,"position":193,"children":194,"psChildren":195},41,"link",{"fr":192},"Expertise",0,[],[],{"id":197,"type":190,"label":198,"href":75,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":30,"position":161,"children":199,"psChildren":200},42,{"fr":74},[],[],{"id":202,"type":190,"label":203,"href":36,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":30,"position":204,"children":205,"psChildren":206},43,{"fr":35},2,[],[],{"id":208,"type":190,"label":209,"href":211,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":30,"position":212,"children":213,"psChildren":214},44,{"fr":210},"Outils IA","\u002Foutils-ia",3,[],[],{"id":216,"type":190,"label":217,"href":29,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":219,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":30,"position":220,"children":221,"psChildren":222},45,{"fr":218},"Offre Starter ✨",{"highlight":20},4,[],[],{"id":224,"type":190,"label":225,"href":78,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":30,"position":137,"children":226,"psChildren":227},46,{"fr":77},[],[],{"id":229,"type":190,"label":230,"href":96,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":30,"position":231,"children":232,"psChildren":233},47,{"fr":92},6,[],[],{"id":235,"type":190,"label":236,"href":102,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":30,"position":237,"children":238,"psChildren":239},48,{"fr":101},7,[],[]]