Préambule
La relance des “paniers abandonnés” en e-commerce, tout le monde en a entendu parler.
Pour la mettre en place sur Prestashop, il suffit en principe de prendre un module et de l’installer pour que cela fasse le travail… sauf que… lorsque l’on parle de Prestashop il se passe un phénomène assez surprenant. Le développeur pose 3 semaines de congés, l’intégrateur tombe malade et vos collègues vous évitent dans les couloirs. Oui, le module que vous venez d’acheter va probablement vous donner du fil à retordre.
En tout cas, de mon côté, j’ai préféré une autre approche !
C’est d’ailleurs pour éviter cette situation embarrassante que j’ai décidé de mettre en place un scénario d’envoi automatique d’email. Mes relances de paniers abandonnés sont alors envoyés à l’aide de Sendinblue et de sa fonctionnalité de marketing automation. Nous installerons un module gratuit fourni par Sendinblue, qui n’est pas trop invasif et surtout ne nécessite aucune configuration complexe.
Principe de fonctionnement
Le principe est simple, on va dans la partie “automation” de Sendinblue, on créer un nouveau scénario et on choisit “Panier abandonné”.
Simple, mais trop beau pour être vrai !
Tout se complique maintenant, Sendinblue vous demande les déclencheurs de votre scénario. Hors ces déclencheurs vous ne les avez pas encore mis en place sur votre site. D’ailleurs, à cet instant, il est probable que vous ne sachiez pas du tout de quoi je parle.
Pas de panique, enfin pas encore !
Revenons en à nos moutons :
Pour commencer, Il vous faut des événements déclencheurs afin de pouvoir entrer dans le scénario, en gros un internaute ajoute un produit dans son panier et c’est là que votre système d’envoi automatique de paniers abandonnés se met en route. Néanmoins pour avoir un email avec un contenu suffisamment pertinent pour ce type de relance qui soit utile pour vos internautes, nous devons trouver un moyen d’y insérer son panier tel qu’il était rempli sur votre site internet. D’ailleurs, nous n’oublierons pas le lien pour que l’internaute puisse y accéder facilement. Normalement, à ce moment, vous comprenez que nous avons besoin que toutes ces informations dans Sendinblue.
Dans ce billet nous allons regarder comment faire cela avec les codes JavaScript proposés par Sendinblue, une dose de courage et un soupçon de patience.
La technique, c’est maintenant
Installation du module Sendinblue pour prestashop (v1.6)
Pour installer le module Sendinblue sur votre boutique, je ne vais rien ré-inventer. La documentation de Sendinblue est très bien faite, je vous invite à suivre la procédure. Une petite subtilité à noter : la clé d’API attendue qui se trouve dans votre compte Sendinblue sera la clé V2.
Identification des actions à remonter
Le module prestashop étant configuré et le marketing automation activé. Nous allons pouvoir passer à la partie code.
Voici les actions de nos internautes qui nous intéressent et que nous allons utiliser :
- L’ajout de produits dans le panier (add_basket)
- La création d’un panier (cart_created)
- La confirmation de commande (order_confirmation)
À l’usage, il y a un problème important avec les étapes identifiées ci-dessus. En effet, il faut que l’internaute soit identifié par Sendinblue (email connu) pour pouvoir lier ses actions sur le site et le scénario que l’on mettra en place par la suite dans Sendinblue. Si une action remonte sans que l’internaute ne soit reconnu grâce à son adresse email, l’action ne sera pas utilisable.
Le module Prestashop réalisé par Sendinblue est plutôt bien fait et va nous aider pour l’identification. Toutefois, lors de leur première visite, de nombreux internautes ajouterons des produits directement dans leur panier avant même de créer un compte. C’est à vrai dire le cas de tous vos nouveaux clients. Pour répondre à ce modèle de parcours client, nous devons ajouter une étape supplémentaire afin de déclencher une action d’identification spécifiquement pour Sendinblue, à savoir :
La connexion à un compte dans le tunnel de conversion du panier (order_logged)
Cette étape survient juste avant le choix des adresses de facturation et de livraison.
Quand Javascript, .php et .tpl s’entremêlent !
Allez c’est le moment de sortir le développeur du placard.
blockcart/ajax-cart.js
Il faut créer un override du fichier /modules/blockcart/ajax-cart.js dans votre thème /themes/[votre-theme]/js/blockcart/ajax-cart.js
Rechercher la méthode Ajax d’ajout d’un produit au panier qui doit se trouver aux alentours de la ligne 207 avec le terme de recherche suivant : controller=cart&add=1&ajax=true
vous devriez trouver sans trop de problème dans la méthode success de l’appel Ajax c’est à dire après l’accolade il faut ajouter le code suivant :
//Script de stockage des informations du panier sendinblue.track( 'cart_created', { "FIRSTNAME": "John", "LASTNAME" : "Doe", }, { "id": jsonData.cart_id, "data": jsonData } ); // Script pour l'ajout au panier window.sendinblue.track( 'add_basket', { 'id_product':idProduct, 'quantity':quantity } );
Vous devriez avoir quelque choses ressemblant à cela :
success: function(jsonData,textStatus,jqXHR) { //Script de stockage des informations du panier sendinblue.track( 'cart_created', { "FIRSTNAME": "John", "LASTNAME" : "Doe", }, { "id": jsonData.cart_id, "data": jsonData } ); // Script pour l'ajout au panier window.sendinblue.track( 'add_basket', { 'id_product':idProduct, 'quantity':quantity } );
Inutile de vous attarder sur les attributs nom et prénom nous ne nous en servirons pas.
Cet appel javascript sera l’un de nos déclencheurs du scénario “Panier abandonné”.
OrderController.php
faire l’override dans le dossier override/controller/front/
Dupliquer la méthode public function initContent()
Rechercher case OrderController::STEP_PAYMENT:
Ajouter dans ce bloc avant $this->_assignSummaryInformations();
le code suivant :
$this->context->smarty->assign(array( 'total_paid_tax_incl' => number_format($this->context->cart->getOrderTotal(true), 2, '.', ' '), 'id_order' => $this->context->cart->id, ));
OrderConfirmationController.php
faire l’override dans le dossier override/controller/front/
Dupliquer la méthode public function initContent()
Modifier le number_format de la variable total_paid_tax_incl pour qu’on puisse l’envoyer à sendinblue et l’utiliser dans le template email. (attention vous aurez probablement un effet de bord sur l’affichage du site)
'total_paid_tax_incl' => number_format($order->total_paid_tax_incl, 2, '.', ' '),
Les informations suivantes pourront ensuite être remontées à Sendinblue dans les étapes de validation d’adresse, de paiement et de confirmation de commande.
order-address.tpl
Chemin – /themes/[votre-theme]/order-address.tpl
Ajoutez ce code avant les appels Js et Css logiquement avant la balise smarty
{strip}
<script> {literal} sendinblue.track( 'cart_created', { "FIRSTNAME": "Votre", "LASTNAME" : "Panier", }, { "id": {/literal}{$cart->id}{literal}, "data": {/literal}{$jsonProducts}{literal} } ); sendinblue.track( 'order_logged', { "id": "{/literal}{$cart->id}{literal}", "data": {'id_customer': {/literal}{$cart->id_customer}{literal}} } ); {/literal} </script>
order-payment.tpl
Chemin – /themes/[votre-theme]/order-payment.tpl
Ajouter ce code en fin de fichier
<script> {literal} $('p.payment_module').on('click', function(event){ sendinblue.track( 'order_confirmation', { "id": "{/literal}{$id_order}{literal}", "data": {'value': {/literal}{$total_paid_tax_incl}{literal},}, } ); handler.selectOption($(this)); return; }); {/literal} </script>
blockcart.php
Faire l’override dans le dossier override/modules/blockcart/
La méthode qu’il faut override est “assignContentVars“ ce que l’on veut absolument c’est l’ajout du hash pour préparer l’auto connexion pour le ré-affichage du panier.
public function assignContentVars($params) { global $errors; // Set currency if ((int)$params['cart']->id_currency && (int)$params['cart']->id_currency != $this->context->currency->id) $currency = new Currency((int)$params['cart']->id_currency); else $currency = $this->context->currency; $taxCalculationMethod = Group::getPriceDisplayMethod((int)Group::getCurrent()->id); $useTax = !($taxCalculationMethod == PS_TAX_EXC); $products = $params['cart']->getProducts(true); $nbTotalProducts = 0; foreach ($products as $product) $nbTotalProducts += (int)$product['cart_quantity']; $cart_rules = $params['cart']->getCartRules(); if (empty($cart_rules)) $base_shipping = $params['cart']->getOrderTotal($useTax, Cart::ONLY_SHIPPING); else { $base_shipping_with_tax = $params['cart']->getOrderTotal(true, Cart::ONLY_SHIPPING); $base_shipping_without_tax = $params['cart']->getOrderTotal(false, Cart::ONLY_SHIPPING); if ($useTax) $base_shipping = $base_shipping_with_tax; else $base_shipping = $base_shipping_without_tax; } $shipping_cost = Tools::displayPrice($base_shipping, $currency); $shipping_cost_float = Tools::convertPrice($base_shipping, $currency); $wrappingCost = (float)($params['cart']->getOrderTotal($useTax, Cart::ONLY_WRAPPING)); $totalToPay = $params['cart']->getOrderTotal($useTax); if ($useTax && Configuration::get('PS_TAX_DISPLAY') == 1) { $totalToPayWithoutTaxes = $params['cart']->getOrderTotal(false); $this->smarty->assign('tax_cost', Tools::displayPrice($totalToPay - $totalToPayWithoutTaxes, $currency)); } // The cart content is altered for display foreach ($cart_rules as &$cart_rule) { if ($cart_rule['free_shipping']) { $shipping_cost = Tools::displayPrice(0, $currency); $shipping_cost_float = 0; $cart_rule['value_real'] -= Tools::convertPrice($base_shipping_with_tax, $currency); $cart_rule['value_tax_exc'] = Tools::convertPrice($base_shipping_without_tax, $currency); } if ($cart_rule['gift_product']) { foreach ($products as $key => &$product) { if ($product['id_product'] == $cart_rule['gift_product'] && $product['id_product_attribute'] == $cart_rule['gift_product_attribute']) { $product['total_wt'] = Tools::ps_round($product['total_wt'] - $product['price_wt'], (int)$currency->decimals * _PS_PRICE_DISPLAY_PRECISION_); $product['total'] = Tools::ps_round($product['total'] - $product['price'], (int)$currency->decimals * _PS_PRICE_DISPLAY_PRECISION_); if ($product['cart_quantity'] > 1) { array_splice($products, $key, 0, array($product)); $products[$key]['cart_quantity'] = $product['cart_quantity'] - 1; $product['cart_quantity'] = 1; } $product['is_gift'] = 1; $cart_rule['value_real'] = Tools::ps_round($cart_rule['value_real'] - $product['price_wt'], (int)$currency->decimals * _PS_PRICE_DISPLAY_PRECISION_); $cart_rule['value_tax_exc'] = Tools::ps_round($cart_rule['value_tax_exc'] - $product['price'], (int)$currency->decimals * _PS_PRICE_DISPLAY_PRECISION_); } } } } $total_free_shipping = 0; if ($free_shipping = Tools::convertPrice(floatval(Configuration::get('PS_SHIPPING_FREE_PRICE')), $currency)) { $total_free_shipping = floatval($free_shipping - ($params['cart']->getOrderTotal(true, Cart::ONLY_PRODUCTS) + $params['cart']->getOrderTotal(true, Cart::ONLY_DISCOUNTS))); $discounts = $params['cart']->getCartRules(CartRule::FILTER_ACTION_SHIPPING); if ($total_free_shipping < 0) $total_free_shipping = 0; if (is_array($discounts) && count($discounts)) $total_free_shipping = 0; } $this->smarty->assign(array( 'products' => $products, 'cart_id' => (int)$params['cart']->id, 'hash' => md5(_COOKIE_KEY_.'recover_cart_'.(int)$params['cart']->id), 'customizedDatas' => Product::getAllCustomizedDatas((int)($params['cart']->id)), 'CUSTOMIZE_FILE' => Product::CUSTOMIZE_FILE, 'CUSTOMIZE_TEXTFIELD' => Product::CUSTOMIZE_TEXTFIELD, 'discounts' => $cart_rules, 'nb_total_products' => (int)($nbTotalProducts), 'shipping_cost' => $shipping_cost, 'shipping_cost_float' => $shipping_cost_float, 'show_wrapping' => $wrappingCost > 0 ? true : false, 'show_tax' => (int)(Configuration::get('PS_TAX_DISPLAY') == 1 && (int)Configuration::get('PS_TAX')), 'wrapping_cost' => Tools::displayPrice($wrappingCost, $currency), 'product_total' => Tools::displayPrice($params['cart']->getOrderTotal($useTax, Cart::BOTH_WITHOUT_SHIPPING), $currency), 'total' => Tools::displayPrice($totalToPay, $currency), 'order_process' => Configuration::get('PS_ORDER_PROCESS_TYPE') ? 'order-opc' : 'order', 'ajax_allowed' => (int)(Configuration::get('PS_BLOCK_CART_AJAX')) == 1 ? true : false, 'static_token' => Tools::getToken(false), 'free_shipping' => $total_free_shipping )); if (count($errors)) $this->smarty->assign('errors', $errors); if (isset($this->context->cookie->ajax_blockcart_display)) $this->smarty->assign('colapseExpandStatus', $this->context->cookie->ajax_blockcart_display); }
En théorie et si je n’ai pas oublié de vous faire ajouter un override, on a ce qu’il nous faut pour passer à la partie Sendinblue.
Mise en place du scénario Sendinblue
Nous allons réaliser le scénario ci-après ensemble. Nous avons notre objectif mais avant le côté sexy du scénario nous devons préparer l’email.
Création du template email
Aller dans l’onglet “Automation” de Sendinblue et cliquer sur “Templates d’emails”
Puis ajouter un nouveau template et préparer sa configuration pour pouvoir passer à l’onglet conception.
Pour le moment je n’ai pas trouvé de solution pour utiliser le système de template en glisser déposé de Sendinblue. les boucle (for each) qui permette de boucler sur les données remontées par Prestashop dans Sendinblue ne sont pas applicable à un bloc complet et reste difficile à utiliser. Il est impossible de fabriquer un récapitulatif du panier de l’internaute éditable par tout un chacun pour l’instant. Pour le coup, nous allons créer un template email à partir d’un fichier html déjà fait que vous adapterez ensuite à votre goût.
Pour cela, utilisons un template responsive créé par un guru de l’email Ted Goas un simple CTRL + S sur cette page vous permettra de le récupérer (Github du projet Cerberus email).
Ouvrez-le avec un éditeur de texte classique et recherchez en fin de fichier les lignes suivantes :
</table> <!-- Email Body : END --> Juste avant insérez le code suivant <!-- Dynamique Cart : END --> <tr style="border-top: solid 10px #F7F5F5;"> <td align="center" valign="top"> <div> <!--[if mso]> <table align="left" border="0" cellspacing="0" cellpadding="0" width="100%" style="width:100%;"> <tr> <![endif]--> <!--[if mso]> <td valign="top" width="590" style="width:590px;"> <![endif]--> <table class="rnb-del-min-width" width="100%" cellpadding="0" border="0" cellspacing="0" style="min-width:100%;" name="Layout_7"> <tbody> <tr> <td class="rnb-del-min-width" align="center" valign="top" style="padding: 20px 0;"> <table width="100%" border="0" cellpadding="0" cellspacing="0" class="rnb-container" bgcolor="#ffffff" style="background-color: rgb(255, 255, 255); padding-left: 20px; padding-right: 20px; border-collapse: separate; border-radius: 0px; border-bottom: 0px none rgb(200, 200, 200);"> <tbody> {% for product in params.products %} <tr> <td width="20%" style="padding: 10px; text-align: left; font-size:14px;color:#696969;border-bottom: solid 10px #F7F5F5;" class="stack-column-center"> <a href="{{product.link}}" title="Lien vers le produit {%autoescape off%}{{product.name}}{%endautoescape%}" style="text-decoration:none;text-align: left; font-size:16px;color:#696969;"> <img src="{%autoescape off%}{{product.image_cart}}{%endautoescape%}" alt="visuel de {%autoescape off%}{{product.name}}{%endautoescape%}" width="" height="" style="font-family: sans-serif, 'Montserrat'; font-size: 16px; line-height: 120%; color: #F59615;"/> </a> </td> <td width="40%" style="padding: 10px; text-align: left; font-size:12px;color:#696969;border-bottom: solid 10px #F7F5F5;" class="stack-column-center"> <a href="{{product.link}}" title="Lien vers le produit {%autoescape off%}{{product.name}}{%endautoescape%}" style="text-decoration:none;text-align: left; font-size:12px;color:#696969;"> <span style="font-family: sans-serif, 'Montserrat'; font-size: 16px; line-height: 120%; color: #F59615;font-weight:bold;">{%autoescape off%}{{product.name}}{%endautoescape%}</span><br /> <span style="text-align: left; font-family: sans-serif, 'Montserrat'; font-size: 12px; line-height: 120%; color: #242424;font-weight:normal;">{%autoescape off%}{{product.attributes}}{%endautoescape%}</span> </a> </td> <td width="12%" style="padding: 10px; text-align: left; font-size:14px;color:#696969;border-bottom: solid 10px #F7F5F5;" class="stack-column-center"> <a href="{{product.link}}" title="Lien vers le produit {%autoescape off%}{{product.name}}{%endautoescape%}" style="text-decoration:none;text-align: left; font-family: sans-serif, 'Montserrat'; font-size: 16px; line-height: 120%; color: #242424;font-weight:normal;"> {{product.quantity}} </a> </td> <td width="18%" style="padding: 10px; text-align: left; font-size:14px;color:#696969;border-bottom: solid 10px #F7F5F5;" class="stack-column-center"> <a href="{{product.link}}" title="Lien vers le produit {%autoescape off%}{{product.name}}{%endautoescape%}" style="text-decoration:none;text-align: left; font-family: sans-serif, 'Montserrat'; font-size: 16px; line-height: 120%; color: #242424;font-weight:normal;"> {%autoescape off%}{{product.price}}{%endautoescape%} </a> </td> </tr> {% endfor %} <tr> <td width="20%" style="padding: 10px; text-align: left; font-size:14px;color:#696969;" class="stack-column-center"> </td> <td width="40%" style="padding: 10px; text-align: left; font-size:12px;color:#696969;" class="stack-column-center"> </td> <td width="12%" style="padding: 10px; text-align: right; font-size:14px;color:#696969;" class="stack-column-center"> <span style="font-family: sans-serif, 'Montserrat'; font-size: 16px; line-height: 120%; color: #242424;font-weight:bold;">TOTAL :</span> </td> <td width="18%" style="padding: 10px; text-align: left; font-size:14px;color:#696969;" class="stack-column-center"> <span style="font-family: sans-serif, 'Montserrat'; font-size: 16px; line-height: 120%; color: #242424;font-weight:bold;">{%autoescape off%}{{params.total}}{%endautoescape%}</span> </td> </tr> </tbody> </table> </td> </tr> </tbody></table><!--[if mso]> </td> <![endif]--> <!--[if mso]> </tr> </table> <![endif]--> </div> </td> </tr> <!-- Dynamique Cart : END -->
Puis importer le tout dans votre template Sendinblue
Vous devriez obtenir quelque chose qui ressemble à la capture suivante.
Vu comme cela c’est n’est pas dingue mais ca fait le job !
J’ai mis en place une boucle de Sendinblue sur les paramètres qui arriveront de Prestashop.
Pour chaque ligne de produit dans le panier, on ajoutera une ligne de plus dans l’email
Et on aura un joli total à la fin.
Enfin, il manque un CTA (Call To Action) pour renvoyer l’internaute vers son panier comme nous l’avions évoqué dans le paragraphe : “Principe de fonctionnement”.
Il faut ajouter encore un peu de code à la suite de ce que vous avez déjà ajouté :
<!-- CART CTA : BEGIN --> <tr> <td style="background-color: #ffffff;"> <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"> <tr> <td style="padding: 40px 20px;"> <!-- Button : BEGIN --> <table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;"> <tr> <td class="button-td button-td-primary" style="border-radius: 25px; background: #FE9305;"> <a class="button-a button-a-primary" href="https://[Votre site web]/commande?recover_cart={{params.id_cart}}&token_cart={{params.hash}}" style="background: #FE9305; border: 1px solid #FE9305; font-family: sans-serif, 'Montserrat'; font-size: 15px; line-height: 120%; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 25px;">VOIR MON PANIER</a> </td> </tr> </table> <!-- Button : END --> </td> </tr> </table> </td> </tr> <!-- CART CTA : END -->
Vous devez remplacer [Votre site web] par le domaine de votre site e-commerce prestashop.
Si vous avez bien travaillé, vous obtenez ceci :
Création du scénario
Allez dans l’onglet Automation, créez un nouveau scénario et choisissez scénario personnalisé.
Ajouter un point d’entrée > “Activité liée à votre site” > “Événement personnalisé”
Choisissez “Événements personnalisés (Track Event)” et ajoutez dans le champs “add_basket”
Puis faites “OK”
Ajoutez un autre point d’entrée > “Activité liée à votre site” > “Événement personnalisé”
Choisissez “Événement personnalisé (Track Event)” et ajoutez dans le champs “order_logged”
Puis faites “OK”
Ensuite cliquez sur le “+” en dessous des deux points d’entrée que vous avez créés
dans la partie “Condition”, choisissez “Attendre jusqu’à” > “Activité du site web”
Sélectionnez l’option “Jusqu’à ce qu’un Track Event ait lieu” et ajoutez dans le champ “order_confirmation”.
Sélectionnez la durée d’attente de votre panier abandonné (perso j’ai choisi une longue attente d’une journée)
Puis cliquez sur “OK”
Vous devriez avoir deux éléments qui s’affichent : “Oui” et “Non”
cliquez sur le “+” sous le “Non” pour Ajouter “Envoyer un email” puis sélectionnez votre template.
Vous pouvez vous envoyer une copie si vous le souhaitez pour tester ce que vos internautes reçoivent et ajouter une planification pour que les emails partent selon une tranche horaire spécifique.
Cochez la case “Utiliser les données des événements” pour personnaliser l’email,
sélectionnez l’option “Les données du dernier événement reçu”
et Ajoutez dans le champ “cart_created”.
Puis faites “OK”
Et voilà !
Bon, il faut quand même que je vous explique ce que l’on vient de faire ensemble.
Vous venez de créer deux déclencheurs pour votre scénario. C’est à dire que vous dites à Sendinblue de faire entrer les internautes qui ont fait exécuter vos codes javascripts “add_basket” et/ou “order_logged” ensuite votre scénario met les internautes en attente pendant le temps que vous avez choisi (dans mon cas j’ai choisi une journée) si au bout de ce temps d’attente, l’internaute n’a pas envoyé d’action “order_confirmation” l’email de relance de panier abandonné sera envoyé avec les données du dernier envoi javascript “cart_created”. L’email sera rempli avec les dernières informations du panier de l’internaute.
Quelques conseils :
- Pour vos tests, vous pouvez ajouter une restriction sur uniquement votre email avant l’envoi des emails. Ainsi vous pourrez le tester sur vous avant de vous lancer dans le grand bain. Rien ne vous empêche du coup de mettre le délai d’attente à quelques secondes.
- Pendant la phase de tests sur vos prospects et clients, je vous conseille d’être en copie des emails afin de vous assurer que ce qu’ils reçoivent correspond à ce que vous aviez prévu.
- Ajoutez à votre template un tag du style “panier_abandon” pour pouvoir suivre facilement vos statistiques transactionnelles. Les envois de la partie automation apparaissent dans les logs de la partie transactionnelle de Sendinblue.
- Pensez également à remplir un “utm_campaign” suffisamment intelligible pour vos mesures analytics
- N’hésitez pas à vous munir d’un développeur et d’une bonne dose de courage avant de vous lancer dans l’aventure.
- Et si jamais la peur vous bloque, vous pouvez toujours faire appel à nous pour votre projet.