Prestashop : Passer des commandes via l’api

Ce tutoriel est compatible avec les versions de Prestashop suivantes :
1.5 1.6 1.7 1.7.7 1.7.8 8.1 +
Cet article est assez ancien, malgré toute l'attention que j' apporte à mes contenus il est possible que celui-ci ne soit plus d'actualité.
N'hésitez pas à me le signaler si nécessaire via le formulaire de contact.

J’ai récemment du faire des tests de commandes via les Api de Prestashop et je n’ai pas trouvé de script tout fait qui le permettait.
Celui-ci utilise la librairie fournie par Prestashop et disponible sur github : https://github.com/PrestaShop/PrestaShop-webservice-lib/blob/master/PSWebServiceLibrary.php

En voici donc un basique qui va effectuer les actions suivantes :

  • Récupération de l’identifiant client ( création du client si nécessaire )
  • Récupération de l’identifiant de l’adresse du client ( création si nécessaire )
  • Création d’un panier
  • Passage de la commande

Ce script a été exécuté avec succès sur la version 1.7.3.3 de Prestashop et doit donc être compatible avec les versions suivantes.
Je n’ai pas constaté de changement fondamentaux dans l’api par rapport à Prestashop 1.6, pour lequel il devrait également fonctionner ( en changeant les produits )

N’hésitez pas à partager vos retours d’expériences sur l’utilisation de l’api de Prestashop

 
require_once('./PSWebServiceLibrary.php');
 
try {
 
    $host = 'https://yourshop.com';
    $apiKey = 'APIKEY';
 
    $webService = new PrestaShopWebservice($host, $apiKey, false);
 
    /**
     * On stocke ici les variables communes aux commandes créés via l'api
     */
 
      $customerEmail = '[email protected]'; //Email du client ( A dynamiser dans le cadre d'une utilisation complète )
      $carrierName = 'My carrier'; //Nom du transporteur dans prestashop ( utilisé pour récupérer son identifiant )
      $paymentLabel = 'Paiement par chèque'; // Nom du mode de paiement
      $paymentCode = 'ps_checkpayment'; //Module de paiement utilisé
 
    /**
     * Liste des produits qu'on souhaite ajouter au panier
     */
    $products = [
        [
            'reference' => 'demo_12',
            'qty' => 3,
        ],
        [
            'reference' => 'demo_19',
            'qty' => 1,
        ],
        [
            'reference' => 'demo_6',
            'qty' => 2,
            'combination' => [
                'reference' => 'demo_6'
            ]
        ],
    ];
 
    //Paramètres du client
    $customerDatas = [
        'firstname' => 'herve',
        'lastname' => 'test',
        'email' => '[email protected]',
        'passwd' => 'mypassword',
        'note' => 'Customer created with api',
    ];
 
    //Paramètres de l'adresse
    $addressDatas = [
        'alias' => 'addresse api',
        'id_customer' => $customerId,
        'firstname' => 'herve',
        'lastname' => 'test',
        'address1' => 'rue des tests',
        'address2' => 'encore rue des tests',
        'postcode' => '67000',
        'city' => 'strasbourg',
        'phone' => '063656565',
        'id_country' => 8,
    ];
 
 
    //On regarde si le client existe
    $searchCustomerXml = $webService->get([
        'resource' => 'customers',
        'filter' => ['email' => $customerEmail],
    ]);
 
    //Si il existe on récupère l'identifiant
    if (!empty($searchCustomerXml->children()->children())) {
        $customerId = (int)$searchCustomerXml->children()->children()[0]->attributes()['id'][0];
    } //Si il n'existe pas on le créé
    else {
        //Création d'un client
        $clientXml = $webService->get(['url' => $host . 'api/customers?schema=blank']);
        foreach ($clientXml->customer[0] as $nodeKey => $node) {
            if (array_key_exists($nodeKey, $customerDatas)) {
                $clientXml->children()[0]->$nodeKey = $customerDatas[$nodeKey];
            }
        }
        $opt = array('resource' => 'customers');
        $opt['postXml'] = $clientXml->asXML();
        $resultXml = $webService->add($opt);
        $customerId = (int)$resultXml->children()[0]->id;
    }
 
    //Récupération des adresses on part du principe qu'on récupère la première adresse du client si elle existe
    //On regarde si le client existe
    $searchAddressXml = $webService->get([
        'resource' => 'addresses',
        'filter' => ['id_customer' => $customerId],
    ]);
 
    //Si il existe on récupère l'identifiant de la première adresse
    if (!empty($searchAddressXml->children()->children())) {
        $addressId = (int)$searchAddressXml->children()->children()[0]->attributes()['id'][0];
    } //Si il n'existe pas on le créé
    else {
 
        //Création d'un client
        $addressXml = $webService->get(['url' => $host . 'api/addresses?schema=blank']);
        foreach ($addressXml->address[0] as $nodeKey => $node) {
            if (array_key_exists($nodeKey, $addressDatas)) {
                $addressXml->children()[0]->$nodeKey = $addressDatas[$nodeKey];
            }
        }
 
        $opt = array('resource' => 'addresses');
        $opt['postXml'] = $addressXml->asXML();
        $resultXml = $webService->add($opt);
        $addressId = (int)$resultXml->children()[0]->id;
    }
 
    //Création d'un panier
    $cartXml = $webService->get(['url' => $host . 'api/carts?schema=blank']);
 
    //Définition des paramètres par défaut du panier
    //Tout ces paramètres pourront être récupérés de l'api en une fois et stockés
    $cartXml->cart->id_customer = $customerId;
    $cartXml->cart->id_address_delivery = $addressId;
    $cartXml->cart->id_address_invoice = $addressId;
    $cartXml->cart->id_currency = 1; //@Todo récupérer via api
    $cartXml->cart->id_lang = 1; //@Todo récupérer via api
    $cartXml->cart->id_shop = 1; //@Todo récupérer via api
    $cartXml->cart->id_shop_group = 1;
 
    //Identifiant du transporteur à récupérer dynamiquement ( peut bouger en étant édité dans le backoffice )
    $carrierXml = $webService->get(['resource' => 'carriers', 'filter' => ['name' => $carrierName]]);
 
    if (!empty($carrierXml->children()->children()[0]->attributes()['id'][0])) {
        $id_carrier = (int)$carrierXml->children()->children()[0]->attributes()['id'][0];
    } //Si il n'existe pas on le créé
    else {
        exit('Mode de livraison existe pas');
    }
 
    $cartXml->cart->id_carrier = $id_carrier;
 
    //Récupération de identifiants des produits
    $productIds = array();
    foreach ($products as $cartProduct) {
        $productSearchXml = $webService->get([
            'resource' => 'products',
            'filter' => ['reference' => $cartProduct['reference']],
        ]);
 
        if (!empty($productSearchXml->children()->children()[0]->attributes()['id'][0])) {
            $id_product = (int)$productSearchXml->children()->children()[0]->attributes()['id'][0];
        } else {
            //Si pas de produit on ne peut pas continuer
            continue;
        }
 
        if (isset($cartProduct['combination'])) {
            $combinationSearchXml = $webService->get([
                'resource' => 'combinations',
                'filter' => ['reference' => $cartProduct['reference']],
            ]);
            if (!empty($combinationSearchXml->children()->children()[0]->attributes()['id'][0])) {
                $id_product_attribute = (int)$combinationSearchXml->children()->children()[0]->attributes()['id'][0];
            } else {
                $id_product_attribute = 0;
            }
        } else {
            $id_product_attribute = 0;
        }
        $productIds[] = [
            'id_product' => $id_product,
            'id_product_attribute' => $id_product_attribute,
            'qty' => $cartProduct['qty']
        ];
    }
 
    //Insertion des lignes de produits
    foreach ($productIds as $productId) {
        $child = $cartXml->cart->associations->cart_rows->addChild('cart_row');
        $child->id_product = $productId['id_product'];
        $child->id_product_attribute = $productId['id_product_attribute'];
        $child->quantity = $productId['qty'];
        $child->id_adddress_delivery = $addressId;
    }
 
    //Création du panier
    $opt = array('resource' => 'carts');
    $opt['postXml'] = $cartXml->asXML();
    $addCartXml = $webService->add($opt);
 
    //Création d'une commande ( à faire )
    $orderXml = $webService->get(['url' => $host . 'api/orders?schema=blank']);
 
    //On défini les éléments de la commande spécifiques
    $orderXml->order->valid = 1;
    $orderXml->order->module = 'ps_checkpayment'; //Module de paiement
    $orderXml->order->payment = 'Paiement par cheque'; //Libéllé du mode de paiment
 
    //Les éléments communs avec le panier sont récupérés depuis celui-ci
    $orderXml->order->id_address_delivery = $addCartXml->cart->id_address_delivery;
    $orderXml->order->id_address_invoice = $addCartXml->cart->id_address_invoice;
    $orderXml->order->id_cart = $addCartXml->cart->id;
    $orderXml->order->id_customer = $addCartXml->cart->id_customer;
    $orderXml->order->id_carrier = $addCartXml->cart->id_carrier;
    $orderXml->order->id_currency = $addCartXml->cart->id_currency;
    $orderXml->order->id_shop = $addCartXml->cart->id_shop;
    $orderXml->order->id_shop_group = $addCartXml->cart->id_shop_group;
    $orderXml->order->id_lang = $addCartXml->cart->id_lang;
 
    // les lignes des produits sont aussi récupérées depuis le panier
 
    $orderTotal = 0; //Variable pour calculer le total de la commande
    unset($orderXml->order->associations->order_rows->children()[0]); //Suppression de ligne order_detail de démo
    foreach ( $addCartXml->cart->associations->cart_rows->cart_row as $cartRow) {
 
        //On ne traite pas les lignes vides
        if ( $cartRow->id_product == 0 || $cartRow->id_product =="")
            continue;
 
        //On récupère les informations disponibles depuis le panier
        $orderRow = $orderXml->order->associations->order_rows->addChild('order_row');
        $orderRow->product_id = $cartRow->id_product;
        $orderRow->product_attribute_id = $cartRow->id_product_attribute;
        $orderRow->product_quantity = $cartRow->quantity;
 
        //Pour le reste on le récupère depuis les produits
        $productXml = $webService->get(['url' => $host . 'api/products/'.$cartRow->id_product]);
        $orderRow->product_name = $productXml->product->name;
        $orderRow->product_reference = $productXml->product->reference;
        $orderRow->product_price = $productXml->product->price;
 
        //Ajout du montant du produit au total du panier ( Le prix HT * la qté * 1+ Taux de tva )
        $orderTotal += ( (float)$productXml->product->price * (int)$cartRow->quantity ) * 1.20 ;
    }
 
    //Données spécifiques à la commande   
    $orderXml->order->total_paid_real = $orderTotal; //Montant payé réellement 
    $orderXml->order->total_paid = $orderTotal; //Montant payé 
 
    $orderXml->order->total_products = 1; //Montant total produit ht ( obligatoire mais indicatif car recalculé automatiquement )
    $orderXml->order->total_products_wt = 1; //Montant total produit ttc ( obligatoire mais indicatif car recalculé automatiquement )
    $orderXml->order->conversion_rate = 1; // Taux de conversion
 
 
    //Création de la commande
    $opt = array('resource' => 'orders');
    $opt['postXml'] = $orderXml->asXML();
    $addOrderXml = $webService->add($opt);
 
    echo 'Création de la commande '.$addOrderXml->order->id;    
} catch (PrestaShopWebserviceException $e) {
    echo $e->getMessage();
}

Des optimisations et des factorisations sont encore possibles mais cela donne déjà une bonne base fonctionnelle 🙂

27 réflexions sur “Prestashop : Passer des commandes via l’api”

    1. Bonjour,
      Je n’ai pas eut le temps de le tester, mais je pense qu’il doit fonctionner.
      Les changements sur l’api ont été assez minimes entre les 2 versions.
      ( Les produits de l’exemples sont ceux de la version demo de la version 1.7 )

      Cordialement,

  1. Merci pour ce tuto, pour moi sa marche sauf que les frais de livraisons il ne sont pas inclus dans le calcule. et si on devais les ajouter je me demande si sa ne doit être qu’au niveau de l’ajout du Order seulement ou bien d’autres paramètres doivent aussi être crée . ?

    1. Bonjour Kacem,

      Votre question est intéressante.
      J’ai regardé en détail le fonctionnement de la fonction d’ajout d’une commande et il s’avère qu’il n’est pas possible de gérer le montant des frais de livraisons via l’api. ( Du moins pas sans surcharge )
      La fonction addWs de la classe Order ( qui créé une commande via api appelle la fonction PaymentModuleCore::validateOrder)
      Dans celle-ci les montants de livraison et les totaux sont calculés automatiquement à partir des informations du panier.

      Cordialement,
      Hervé

  2. Bonjour,

    j’essaye de mettre en place un ws pour la création de commande. J’ai suivi le tuto mais j’ai une erreur :
    This call to PrestaShop Web Services failed and returned an HTTP status of 500. That means: Internal Server Error.

    Le client est correctement créé.
    Le panier est correctement créé.
    La commande elle, n’est pas créé. Et j’ai vérifié les permissions sont toutes cochés. Une idée de ce qui peut coincer ou de comment débugger cela ?

    Merci

    1. Bonjour Mathieu,
      Est-ce que si vous activez le mode debug sur votre instance le message est plus explicite ?
      Quelle est la version de prestashop ?

      Cordialement,
      Hervé

  3. Merci pour votre retour.
    En fait, c’est en activant le mode débug que j’ai pu récupérer ce message.
    J’indique « true » à PrestaShopWebservice
    $webService = new PrestaShopWebservice($host, $apiKey, true);

    La version de prestashop est la 1.6. Vous indiquez bien ne pas l’avoir tester sur cette version mais je pensais que cela serait proche tout de même et c’est frustrant de pouvoir créer client et panier et de buter sur la commande.

    Je vais essayer de monter un prestashop de test en 1.7.6 pour vérifier sur cette version.

    Bien à vous

    Mathieu

  4. Ah, si, sur le prestashop 1.6, j’ai quand même le retour des XML envoyé, si ca peut donner une piste ?

    HTTP REQUEST HEADER
    POST //api/orders HTTP/1.1
    Authorization: Basic N0JUMlNCUTNGVFZVV1lLNzNLQkNNMlJNNUw0NlVFVUI6
    Host: http://www.xxx.com
    Accept: */*
    Content-Length: 1559
    Content-Type: application/x-www-form-urlencoded

    HTTP RESPONSE HEADER
    HTTP/1.1 500 Internal Server Error
    Date: Wed, 14 Aug 2019 13:11:19 GMT
    Content-Type: text/html; charset=utf-8
    Transfer-Encoding: chunked
    Connection: keep-alive
    Set-Cookie: SERVERID105612=144060; path=/; max-age=900
    Server: Apache
    X-Powered-By: PHP/7.0
    XML SENT

    3657
    3657
    12890
    1
    1
    4130
    66

    ps_checkpayment

    1

    1
    1

    Paiement par cheque

    18

    18
    1
    1

    1

    7403001235.000000

    1. Bonjour,
      La réponse étant échappée par wordpress je n’arrive pas à voir la réponse exacte.
      Il faudrait voir en même temps si vous avez des logs d’erreurs sur votre serveur pour voir la cause de l’erreur.

  5. Bonjour,

    J’ai le même problème que Mathieu sous prestashop 1.6.1.3
    Le client / panier sont OK
    Mais l’order ne fonctionne pas :
    This call to PrestaShop Web Services failed and returned an HTTP status of 500. That means: Internal Server Error

    Je n’arrive pas a afficher plus d’informations sur l’erreur.

    @mathieu as-tu réussi a corrigé ton problème ?

  6. Bonjour,
    J’avais mis se projet en pause mais j’y suis revenu il y a quelques jours…
    Finalement cela fonctionne bien sur Prestashop 1.6 Il faut faire (très) attention a avoir les bonnes valeurs sinon le panier semble ok mais cela coince au niveau de l’order.

    Il me reste une erreur tout de même sur la methode de livraison :
    Quel que soit l’id_carrier que je passe dans le XML de l’order, lorsque la commande est créé sur prestashop il prend le transporteur indiqué par défaut.
    Exemple : je passe l’id 77 qui est le transporteur avec frais de port gratuit et dans la commande il me force l’id 79 qui est le transporteur par défaut à 5.90 € :/

    Si quelqu’un a une idée je suis preneur. Est ce que vous rencontrer le même problème sur Prestashop 1.7 ?

    Merci

    Mathieu

  7. Salut,
    Merci mille fois pour le temps précieux que ce partage me fait gagner.
    Je suis sur une version 1.7.6.2, je vous fais un retour dès que cela fonctionne.
    A très bientôt.
    Gab.

  8. Bonjour,
    Merci pour votre script !
    Malheureusement, celui-ci ne prend pas en compte les frais de port.
    Dans la commande que je tente de passer via l’API, je dois recalculer le total de la commande pour les frais de port.
    L’API demande un champ total_paid_real, qui n’est pas recalculé lors du processing de la commande, et qui est utilisé pour l’enregistrement du paiement de la commande.
    Je me demande s’il s’agit d’un bug du webservice ou d’un comportement normal, mais pour le moment, je peux pas créer une commande 100% valide, celle-ci bascule automatiquement en ‘erreur de paiement’

    1. Bonjour,
      Désolé je n’ai pas eut l’occasion de repasser sur ce script depuis un moment.
      Est-ce que vous avez trouver une solution/un contournement ?

      Cordialement,
      Hervé

  9. Bonjour Hervé, et encore merci pour tout votre travail mis à disposition.
    Je ne parviens pas à faire fonctionner ce script.
    La clé est bien générée, toutes les permissions pour la clef sont générées.
    Si je mets host https://monsite.com j’obtiens une « redirection 302 inattendue » et si je mets https://www.monsite.com j’obtiens une erreur 401 « Unauthorized ».
    Dans le htaccess je vois qu’il y a une règle de redir /api/ vers webservices/dispatcher.php. Je ne sais pas si c’est ça qui bloque…
    Dans la librairie webservice, dans les defaultParams j’ai ajouté les 2 lignes :
    CURLOPT_SSL_VERIFYHOST => 0,
    CURLOPT_SSL_VERIFYPEER => 0
    au cas où ce serait le certificat SSL qui gêne, mais rien…
    Avez-vous une idée ? Merci merci d’avance.
    Renaud.

    1. Bonjour Renaud,
      Vers quelle page se fait la redirection 302 ?
      Je n’ai encore jamais rencontré ce type d’erreur qui doit sans doute être lié à une configuration particulière sur votre serveur.
      Il faudrait tenter en console une commande du type
      curl -I https://[email protected]/api/ pour voir les entêtes de la requête.
      Cordialement,
      Hervé

  10. Mais dans le htaccess à la racine de PS, il y a :
    RewriteRule ^api(?:/(.*))?$ %{ENV:REWRITEBASE}webservice/dispatcher.php?url=$1 [QSA,L]

    Je ne vois pas à quoi sert ce dispatcher.php

  11. Progression ! En ajoutant la ligne
    RewriteRule .* – [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] dans le htaccess et en mettant le www dans $host ça connecte ! Sauf que le débug dit BAD HTTP RESPONSE…
    Dans la librairie je vois que :
    if ($index === false && $curl_params[CURLOPT_CUSTOMREQUEST] != ‘HEAD’) {
    throw new PrestaShopWebserviceException(‘Bad HTTP response’);
    }
    Donc je fais un print_r sur $curl_params et en effet je n’obtiens pas HEAD mais GET…
    A devenir dingue, ce truc !

  12. Bonjour Hervé, j’ai retourné cette doc dans tous les sens. Ce script de test fonctionne parfaitement :
    require_once(‘./PSWebServiceLibrary.php’);
    $host = ‘https://www.vivrenaturellement.com’;
    $apiKey = ‘XXXXXXXXXXXXXXXXXXXX’;

    try {
    // creating webservice access
    $webService = new PrestaShopWebservice($host, $apiKey, true);

    // call to retrieve all customers
    $xml = $webService->get([‘resource’ => ‘customers’]);
    } catch (PrestaShopWebserviceException $ex) {
    // Shows a message related to the error
    echo ‘Other error: ‘ . $ex->getMessage();
    }

    Mais quand je tente d’exécuter votre script: Bad HTTP response
    La loose…

    1. Je pense que dans ce cas il faudra exécuter en step by step les différentes étapes.
      C’est peut être une truc tout bête dans le copier/coller ou un élément spécifique à votre site.
      Les différents commentaires à cet article semblent confirmer le bon fonctionnement global du script je ne sais donc pas trop vous préciser quoi faire de plus.

  13. Merci, Hervé, je fais ça: step by step.
    Par contre, dans votre script, il n’y a pas d’entrée dans order_history c’est normal ?

  14. …je vois aussi que dans votre script vous n’implémentez pas order->current_state…et du coup le current_state va être à 0 non ?
    Désolé d’insister, j’essaie de comprendre.

    1. Bonjour Renaud,
      J’avoue que ce script date un peu je n’ai plus forcément l’ensemble des processus en tête.
      Il faudrait que je reste l’ensemble mais j’ai assez peu de temps pour le faire en ce moment.

    2. Bonjour, avez vous réussi a ajouter le script définissant le statut de la commande ? j’essaie également de le faire mais je n’y parviens pas ! Merci d’avance pour votre retour

Laisser un commentaire

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