Prestashop 1.7 : Ajouter une étape dans le tunnel de commande

Ce tutoriel est compatible avec les versions de Prestashop suivantes :
1.7 1.7.7 +

Dans sa version 1.7 de Prestashop a complètement refondu le fonctionnement du tunnel de commande.

EDIT : 2021-08-16 :
A compter de la version 1.7.8  il n’est plus nécessaire de réaliser une surcharge car un nouveau hook a été implémenté.
Un article sera sans doute rédigé pour mise à jour dès que cette version sortira officiellement.
En attendant les détails peuvent être visualisés ici :
https://github.com/PrestaShop/PrestaShop/pull/19830

Le fonctionnement est plus propre que dans la version précédente et avec cette nouvelle architecture il devient relativement simple d’ajouter une nouvelle étape.
Nous allons voir comment réaliser cela via la création d’un module.
L’idée étant d’ajouter une étape « Test de nouvelle étape » comme sur la capture ci-dessous :

Checkout Nouvelle étape
Affichage de la nouvelle étape

Cet étape contiendra uniquement 2 informations que nous souhaitons pouvoir réutiliser dans le panier

Fonctionnement technique

Les différentes étapes du tunnel de commandes sont gérées dans la méthode  bootstrap du controller OrderController , le code est relativement simple à comprendre.

Un instance de la classe CheckoutProcess est créé.
Puis l’ensemble des steps ( étapes ) sont ensuite ajoutée au checkout via la méthode addStep(CheckoutStepInterface $step) qui attends donc une classe implémentant l’interface CheckoutStepInterface

Voici le code de la fonction du controller natif ( version 1.7.6 de Prestashop )

 protected function bootstrap()
    {
        $translator = $this->getTranslator();
 
        $session = $this->getCheckoutSession();
 
        /**
         * Création du process
         */
        $this->checkoutProcess = new CheckoutProcess(
            $this->context,
            $session
        );
 
        /**
         * Ajout des différentes étapes
         */
        $this->checkoutProcess
            ->addStep(new CheckoutPersonalInformationStep(
                $this->context,
                $translator,
                $this->makeLoginForm(),
                $this->makeCustomerForm()
            ))
            ->addStep(new CheckoutAddressesStep(
                $this->context,
                $translator,
                $this->makeAddressForm()
            ));
 
        if (!$this->context->cart->isVirtualCart()) {
            $checkoutDeliveryStep = new CheckoutDeliveryStep(
                $this->context,
                $translator
            );
 
            $checkoutDeliveryStep
                ->setRecyclablePackAllowed((bool) Configuration::get('PS_RECYCLABLE_PACK'))
                ->setGiftAllowed((bool) Configuration::get('PS_GIFT_WRAPPING'))
                ->setIncludeTaxes(
                    !Product::getTaxCalculationMethod((int) $this->context->cart->id_customer)
                    && (int) Configuration::get('PS_TAX')
                )
                ->setDisplayTaxesLabel((Configuration::get('PS_TAX') && !Configuration::get('AEUC_LABEL_TAX_INC_EXC')))
                ->setGiftCost(
                    $this->context->cart->getGiftWrappingPrice(
                        $checkoutDeliveryStep->getIncludeTaxes()
                    )
                );
 
            $this->checkoutProcess->addStep($checkoutDeliveryStep);
        }
 
        $this->checkoutProcess
            ->addStep(new CheckoutPaymentStep(
                $this->context,
                $translator,
                new PaymentOptionsFinder(),
                new ConditionsToApproveFinder(
                    $this->context,
                    $translator
                )
            ));
    }

Comme vous pouvez le remarque il n’existe pas de hook, ni de solution non invasive pour rajouter une nouvelle étape personnalisée.
Il sera donc nécessaire de surcharger cette méthode pour pouvoir ajouter notre nouvelle étape.

Ajout de la nouvelle étape

C’est donc parti pour ajouter notre nouvelle étape via le module hh_customcheckout

Voici la structure des fichiers que nous allons créer :

Le contenu du fichier hh_customcheckout.php n’a pas d’importance car il sert uniquement à déclarer le fichier afin que la surcharge du controller OrderController soit prise en compte.

Pour commencer il faut créer la classe de gestion CustomCheckoutStep.php de notre étape de checkout , voici son contenu :

<!--?php
 
use Symfony\Component\Translation\TranslatorInterface;
 
class CustomCheckoutStep extends AbstractCheckoutStep
{
 
    /** @var Hh_CustomCheckout */
    protected $module;
 
    /** @var string $serviceName */
    protected $serviceName;
 
    /** @var string $responsableName */
    protected $responsableName;
 
    public function __construct(
        Context $context,
        TranslatorInterface $translator,
        Hh_CustomCheckout $module
    )
    {
        parent::__construct($context, $translator);
        $this--->module = $module;
        $this->setTitle('Test de nouvelle étape');
    }
 
 
    /**
     * Récupération des données à persister
     *
     * @return array step
     */
    public function getDataToPersist()
    {
        return array(
            'service_name' => $this->serviceName,
            'responsable_name' => $this->responsableName,
        );
    }
 
    /**
     * Restoration des données persistées de la step
     *
     * @param array $data
     * @return $this|AbstractCheckoutStep
     */
    public function restorePersistedData(array $data)
    {
        if (array_key_exists('service_name', $data)) {
            $this->serviceName = $data['service_name'];
        }
 
        if (array_key_exists('responsable_name', $data)) {
            $this->responsableName = $data['responsable_name'];
        }
 
        return $this;
    }
 
    /**
     * Traitement de la requête ( ie = Variables Posts du checkout )
     * @param array $requestParameters
     * @return $this
     */
    public function handleRequest(array $requestParameters = array())
    {
        //Si les informations sont postées assignation des valeurs
        if (isset($requestParameters['submitCustomStep'])) {
            $this->serviceName = $requestParameters['service_name'];
            $this->responsableName = $requestParameters['responsable_name'];
 
            //Passage à l'étape suivante
            $this->setComplete(true);
 
            //Code 1.7.6
            if (version_compare(_PS_VERSION_, '1.7.6') > 0) {
                $this->setNextStepAsCurrent();
            } else {
                $this->setCurrent(false);
            }
        }
 
        return $this;
    }
 
    /**
     * Affichage de la step
     *
     * @param array $extraParams
     * @return string
     */
    public function render(array $extraParams = [])
    {
 
        //Assignation des informations d'affichage
        $defaultParams = array(
            //Informations nécessaires
            'identifier' => 'test',
            'position' => 3, //La position n'est qu'indicative ...
            'title' => $this->getTitle(),
            'step_is_complete' => (int)$this->isComplete(),
            'step_is_reachable' => (int)$this->isReachable(),
            'step_is_current' => (int)$this->isCurrent(),
            //Variables custom
            'serviceName' => $this->serviceName,
            'responsableName' => $this->responsableName,
        );
 
        $this->context->smarty->assign($defaultParams);
        return $this->module->display(
            _PS_MODULE_DIR_ . $this->module->name,
            'views/templates/front/customCheckoutStep.tpl'
        );
    }
 
}

Nous pouvons ensuite définir le template front de cet étape dans le fichier customCheckoutStep.tpl dont voici le contenu

 

<!-- Le template doit ếtendre le template parent des étapes du tunnel de commande -->
{extends file='checkout/_partials/steps/checkout-step.tpl'}
 
<!-- Le contenu doit doit être dans le block step content -->
{block name='step_content'}
    <div class="custom-checkout-step">
        <h2>Afin de confirmer votre commande merci de renseigner ces informations complémentaire sur votre service</h2>
        <!-- Le formulaire doit envoyer les données sur la page de commande en post -->
        <form
                method="POST"
                action="{$urls.pages.order}"
                data-refresh-url="{url entity='order' params=['ajax' => 1, 'action' => 'customStep']}"
        >
 
            <!-- Les Champs spécifiques de la step avec assignation de la variable si elle existe -->
            <section class="form-fields">
                <div class="form-group row">
                    <label class="col-md-3 form-control-label required">Nom du service</label>
                    <div class="col-md-6">
                        <input type="text" name="service_name" {if isset($serviceName)}value="{$serviceName}"{/if}/>
                    </div>
                </div>
                <div class="form-group row">
                    <label class="col-md-3 form-control-label required">Nom du responsable</label>
                    <div class="col-md-6">
                        <input type="text" name="responsable_name" {if isset($responsableName)}value="{$responsableName}"{/if}/>
                    </div>
                </div
            </section>
            <footer class="form-footer clearfix">
                <input type="submit" name="submitCustomStep" value="Envoyer"
                       class="btn btn-primary continue float-xs-right"/>
            </footer>
        </form>
    </div>
{/block}

Notre nouvelle étape est maintenant définie et son affichage également, il ne reste plus qu’a l’ajouter dans le checkout à la position souhaitée, ceci sera fait via l’override du fichier OrderController.php dont voici le contenu

 

<!--?php
include_once _PS_MODULE_DIR_ . 'hh_customcheckout/hh_customcheckout.php';
include_once _PS_MODULE_DIR_ . 'hh_customcheckout/classes/CustomCheckoutStep.php';
 
class OrderController extends OrderControllerCore
{
 
    protected function bootstrap()
    {
        $translator = $this--->getTranslator();
 
        $session = $this->getCheckoutSession();
 
        $this->checkoutProcess = new CheckoutProcess(
            $this->context,
            $session
        );
 
        $this->checkoutProcess
            ->addStep(new CheckoutPersonalInformationStep(
                $this->context,
                $translator,
                $this->makeLoginForm(),
                $this->makeCustomerForm()
            ))
            ->addStep(new CheckoutAddressesStep(
                $this->context,
                $translator,
                $this->makeAddressForm()
            ));
 
        //Les steps sont affichée dans l'ordre d'ajout et il n'y a pas de possibilité de modifier l'ordre
        //Du coup il faut l'ajouter dans la position souhaitée
        $customCheckout = Module::getInstanceByName('hh_customcheckout');
        $this->checkoutProcess
            ->addStep(new CustomCheckoutStep(
                    $this->context,
                    $this->getTranslator(),
                    $customCheckout
                )
            );
 
        if (!$this->context->cart->isVirtualCart()) {
            $checkoutDeliveryStep = new CheckoutDeliveryStep(
                $this->context,
                $translator
            );
 
            $checkoutDeliveryStep
                ->setRecyclablePackAllowed((bool) Configuration::get('PS_RECYCLABLE_PACK'))
                ->setGiftAllowed((bool) Configuration::get('PS_GIFT_WRAPPING'))
                ->setIncludeTaxes(
                    !Product::getTaxCalculationMethod((int) $this->context->cart->id_customer)
                    && (int) Configuration::get('PS_TAX')
                )
                ->setDisplayTaxesLabel((Configuration::get('PS_TAX') && !Configuration::get('AEUC_LABEL_TAX_INC_EXC')))
                ->setGiftCost(
                    $this->context->cart->getGiftWrappingPrice(
                        $checkoutDeliveryStep->getIncludeTaxes()
                    )
                );
 
            $this->checkoutProcess->addStep($checkoutDeliveryStep);
        }
 
        $this->checkoutProcess
            ->addStep(new CheckoutPaymentStep(
                $this->context,
                $translator,
                new PaymentOptionsFinder(),
                new ConditionsToApproveFinder(
                    $this->context,
                    $translator
                )
            ));
 
    }
 
}

Une fois le module installé, notre nouvelle étape sera bien disponible dans le tunnel de commande 😀
Pour ceux qui le souhaitent vous pouvez télécharger directement le fichier du module , pour l’instant je ne l’ai testé sur  les versions  1.7.5 et supérieur

hh_customcheckout

16 réflexions sur “Prestashop 1.7 : Ajouter une étape dans le tunnel de commande”

  1. Bonjour Hervé,

    J’ai installé votre module pour ajouter une étape au processus de commande sur prestashop 1.7.
    Aucun souci sur l’installation, l’étape s’affiche correctement.
    Seulement les deux champs n’apparaissent ni en back office, ni dans le mail de confirmation de commande. Ai-je raté quelque chose ?

    Merci d’avance de votre retour.

    Fred,

    1. Bonjour Fred,

      Tout à fait ce module est un POC pour ajouter une step dans le tunnel de commande.
      Ce n’est pas un module à utiliser directement en production des développements additionnels sont nécessaires.
      Pour la récupération des informations vous pouvez les trouver dans la colonne checkout_session_data du panier lié à la commande.

      Cordialement,
      Hervé

  2. Rebonjour,

    Merci pour cette réponse rapide.
    C’est bien ce qu’il me semblait.

    Je reçois bien les infos dans la bdd, pas de souci.
    En revanche, je ne suis pas assez aguerri pour récupérer ces infos et les inclure dans le mail order_conf. Dommage. C’était presque top !

  3. Bonjour Hervé, j’ai suivi votre tutoriel pour ajouter une étape au checkout. Tout est fonctionnel, le template s’affiche correctement mais les datas ne sont pas enregistré dans la colonne « checkout_session_data » de la table « cart ».
    Avez-vous aussi ce problème ?
    Version 1.7.6

    1. Bonjour Aymeric,
      Je n’ai pas constaté de problèmes de ce type.
      J’ai eut des retours récemment sur l’utilisation de ce tutoriel pour faire un module qui fonctionne très bien sur cette version.
      Avez-vous bien suivi l’ensemble des étapes ?

      Cordialement,
      Hervé

  4. Bonjour,

    Merci pour ce tuto, j’ai une question annexe, est-t-il possible de changer l’url pour chaque étape ou d’ajouter des ancres ? ceci est pour des raisons GA

    1. Bonjour Nizar,
      Les étapes sont gérées nativement pas le module google analytics de prestashop il me semble.
      Il faudrait voir comment c’est configuré, je ne saurais pas vous dire sur ce point.

      Cordialement,
      hervé

  5. Ca fonctionne super comme toujours, petite question sur le checkout, est-ce qu’il y a moyen de rediriger le client vers une étape spécifique du tunnel après une action ? J’ajoute un produit au panier depuis le tunnel et je souhaite revenir vers une étape précise juste après. Mais cela ne fonctionne pas. Merci 🙂

    1. Bonjour,

      Difficile de vous répondre avec exactitude car tout dépends :
      en quelle position est votre étape ?
      et si les informations nécessaires aux étapes qui la précède sont définies ou pas.

    1. Bonjour,

      Non malheureusement.
      L’ajout d’étapes sur les version 1.6 est moins pratique car il n’existe pas cette notion de steps.
      Il va falloir surcharger le controller OrderCOntroller et rajouter vos étapes dedans ( ou OrderOpcController en fonction du tunnel utilisé )

      Cordialement,
      Hervé

  6. Bonjour Hervé,

    Merci de votre partage, cependant j’ai une question. Je souhaite ajouter une nouvelle étape dans mon tunnel de commande. J’ai donc suivi votre tutoriel, il fonctionne parfaitement puisqu’il m’a ajouté une nouvelle étape 3.

    Cependant je souhaite plutôt l’ajouter en étape numéro 1.

    J’ai donc déplacé ce code de place dans OrderController.php :

    $customCheckout = Module::getInstanceByName(‘hh_customcheckout’);
    $this->checkoutProcess
    ->addStep(new CustomCheckoutStep(
    $this->context,
    $this->getTranslator(),
    $customCheckout
    )
    );

    Je l’ai ajouté juste après ça (position 3 à 1)

    $this->checkoutProcess = new CheckoutProcess(
    $this->context,
    $session
    );

    Ainsi j’ai bien votre formulaire qui arrive en première étape. Malheureusement lorsque je ne suis pas connecté, le bouton « envoyer » pour passer à l’étape suivante ne fonctionne pas. Si je suis connecté, cette étape est sautée et j’arrive directement au choix de l’adresse / transporteur.

    Avez-vous une idée pour corriger cela ?

    Merci d’avance et bonne journée,

    Vincent,

    1. Bonjour Vincent,

      Merci pour votre retour.
      De mon côté j’ai uniquement été amené à ajouter des étapes après les 2 premières.
      Je n’ai donc malheureusement pas d’expérience ou de solution concrète à vous donner sur ce point.
      J’imagine que si ce n’est pas fonctionnel il va falloir surcharger les autres étapes, pour conditionner ce qui passe leur statut à step_is_current et step_is_complete

      Cordialement,
      Hervé

  7. Hi Hervé,
    the module works perfectly the only problem I have is, after confirming the order and when I place a new order, the custom step reproduces the same data entered in the previous order.
    Is it a problem you have encountered?
    How to reset the data of the custom step so that they are not re-proposed in the event of a new order in the same browsing session?
    Kind Regards
    Alex

    1. Hi Alex,

      Thanks for the feedback.
      I admit that i’ve not thinked about this issue.
      I see no other way to fix this, than altering the function \CartCore::duplicate ( by adding a custom hook for exemple ) to clean this data after the cart have been duplicated.

      Regards,
      Hervé

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *