Intégrer PayPal Express Checkout à son site en PHP

PrimFX Boris ('PrimFX') Le 10 mars 2018

Vendre différents produits

Vendre différents produits, comme ceux d’un catalogue ou d’une liste de produits bien définie par exemple, est tout à fait possible avec PayPal Express Checkout. Contrairement à la vente d’un produit unique comme nous l’avons fait jusqu’à présent, un paramètre va venir s’ajouter ici : le produit, ou plutôt sa référence.

Pour l’instant, notez bien que l’on s’occupe de vendre différents produit, mais seulement un à la fois, sans « panier de produits ». Nous aborderons ce cas dans la partie suivante 😉

Intégration du système

Pour travailler avec une liste de produits, comme vous vous en doutez certainement, nous passerons par l’intermédiaire d’une nouvelle table de notre base de données que nous appellerons « produits ». Voici à quoi ressemblera cette table :

En SQL :

CREATE TABLE `produits` (
  `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `ref` varchar(10) NOT NULL,
  `titre` varchar(255) NOT NULL,
  `description` text NOT NULL,
  `prix` float NOT NULL
)

Bien sûr, nous restons ici dans un modèle de catalogue de produits très simpliste, le but étant toujours de bien comprendre le fonctionnement du système de paiement PayPal Express Checkout et non de créer entièrement un site de e-commerce 😛 Libre à vous donc d’ajouter des champs voire tables annexes / complémentaires, comme par exemple un champ « stock » indiquant le nombre de produits disponibles, un système de catégories de produits, un champ devise si des produits venaient à être vendus dans différentes devises, une table clients avec les informations des clients (nom, prénom, email, adresse postale, ...), etc.

Dernière petite modification au niveau de la base de données : il nous faudra désormais associer un produit à chaque paiement, ou plutôt la référence de ce produit. On peut donc ajouter un champ « produit » (par exemple, le nom est bien sûr arbitraire) dans notre table « paiements » qui permettra de stocker la référence du produit acheté (champ « ref » dans la table « produits »). Voici à quoi ressemble donc à présent la table « paiements » :

En SQL, voici la modification effectuée :

ALTER TABLE `paiements` ADD `produit` VARCHAR(10) NOT NULL AFTER `id`;

J’ai ajouté le champ « produit » en début de table, juste après l’id, afin de le mettre en évidence. Libre à vous bien sûr de le rajouter en fin de table ou ailleurs 😉 Vous remarquerez également que j’ai pris soin de définir le type de « produit » en VARCHAR(10) qui est strictement le même que celui du champ « ref » de la table produit, étant donné que ce sera la valeur de « ref » qui représentera nos produits dans le champ « produit » de la table « paiements ».

Oui je sais, je me répète un peu, mais je préfère être sûr que vous ne soyez pas perdu avec cette gymnastique de noms de champs / tables que nous venons de mettre en place 😅

A présent, dans le cadre de nos tests, je vous propose d’ajouter quelques produits dans notre base de données. J’en créerai ici juste deux, il n’y a pas besoin d’en avoir des dizaines pour mettre en place et tester le système. Les références de ces produits seront d’ailleurs tout à fait arbitraires, de même que les titres, descriptions, prix, etc.

 

Comme nous travaillerons toujours avec principalement 3 fichiers, il faudra modifier d’une part le côté client, puis nos scripts de création et d'exécution de paiements côté serveur. A noter que la seule véritable modification par rapport à notre processus de paiement déjà existant sera l’ajout d’un paramètre permettant d’identifier le produit qui devra transiter dans nos scripts côté serveur.

Au niveau du fichier côté client, nous appelons pour créer un paiement le script paypal_create_payment.php. Afin de lui indiquer quel produit nous souhaitons acheter, il suffira de passer un paramètre d’URL directement dans le chemin d’appel du fichier avec par exemple la référence (champ « ref ») de notre produit. C’est tout, rien de plus à éditer côté client !

Nous allons donc procéder de la façon suivante : en se basant sur la page « index.html » que nous avions créé précédemment, nous allons créer une nouvelle page « produit.php » dans laquelle nous allons simplement afficher un produit dont l’ID sera passé en paramètre d’URL. Nous nous chargerons ensuite de passer la référence de ce produit en paramètre lors de l’appel de notre script de création de paiement. Voici donc à quoi ressemblera notre code côté client :

<?php
require_once "php/config.php"; // On inclue notre fichier de configuration
// On vérifie si un paramètre d'URL "id" est bien présent, celui-ci représentant le produit à afficher sur la page
if (!empty($_GET['id'])) {
  // On tente de récupérer 
  $produit = $bdd->prepare('SELECT * FROM produits WHERE id = ?');
  $produit->execute(array((int) $_GET['id'])); // On force la conversion en (int) de notre variable $_GET de façon à la sécuriser
  $produit = $produit->fetch(PDO::FETCH_ASSOC);
  if (!$produit) {
    exit("Erreur 404 :/"); // Si le produit n'est pas trouvé : erreur
  }
} else {
  // Si le paramètre $_GET['id'] n'existe pas / ne contient rien : erreur
  exit("Erreur 404 :/");
}
?>
<html>
<head>
  <title><?= $produit['titre'] ?></title>
  <meta charset="utf-8">
  <script src="https://www.paypalobjects.com/api/checkout.js"></script>
</head>
<body>
  
  <!-- On affiche quelques informations sur notre produits -->
  <h1><?= $produit['titre'] ?></h1>
  <h2>Description</h2>
  <p><?= $produit['description'] ?></p>
  <div id="bouton-paypal"></div>
  <script>
    paypal.Button.render({
      env: 'sandbox',
      commit: true,
      style: {
        color: 'blue',
        size: 'small'
      },
      payment: function() {
        var CREATE_URL = 'php/paypal_create_payment.php?ref=<?= $produit["ref"] ?>';
        /* On passe ici la référence de notre produit, il s'agit de la modification la plus importante !
        Vous remarquerez que j'ai pris soin de laisser le guillemet simple après le echo de la référence en PHP. C'est très important : il faut que JS interprète bien notre référence comme étant à l'intérieur de la chaîne de caractère. */
        return paypal.request.post(CREATE_URL)
          .then(function(data) {
          console.log(data);
          if (data.success) {
            return data.paypal_response.id;
          } else {
            alert(data.msg);
            return false;
          }
        });
      },
      onAuthorize: function(data, actions) {
        var EXECUTE_URL = 'php/paypal_execute_payment.php';
        // Ici, pas besoin de passer à nouveau la référence du produit : nous l'aurons déjà lié au paiement en cours dans notre base de données dans le script de créaton de paiement ! (A un payment_id unique correspondra forcément la référence d'un produit dans la table "paiements")
        var data = {
          paymentID: data.paymentID,
          payerID: data.payerID
        };
        return paypal.request.post(EXECUTE_URL, data)
          .then(function (data) {
          console.log(data);
          if (data.success) {
            alert("Paiement approuvé ! Merci !");
          } else {
            alert(data.msg);
          }
        });
      },
      onCancel: function(data, actions) {
        alert("Paiement annulé : vous avez fermé la fenêtre de paiement.");
      },
      onError: function(err) {
        alert("Paiement annulé : une erreur est survenue. Merci de bien vouloir réessayer ultérieurement.");
      }
    }, '#bouton-paypal');
  </script>
</body>
</html>

A présent, il va nous falloir récupérer ce paramètre d’URL dans notre fichier paypal_create_payment.php, mais également effectuer quelques vérifications sur celui-ci. On le récupérera donc via $_GET['ref'] et on cherchera dans notre base de données le produit correspondant à cette référence. On pourra ensuite créer notre paiement en précisant dans l’item envoyé à PayPal les données de notre produit récupérées directement en base de données. Voici à quoi devrait donc ressembler le code de paypal_create_payment.php (j’ai directement commenté les modifications effectuées par rapport à l’ancienne version de ce script 😉) :

<?php
require_once "config.php";
require_once "../class/PayPalPayment.php";
$success = 0;
$msg = "Une erreur est survenue, merci de bien vouloir réessayer ultérieurement...";
$paypal_response = [];
// On vérifie la présence de la référence du produit en paramètre d'URL ($_GET)
if (!empty($_GET['ref'])) {
   // On sécurise un minimum cette variable d'URL
   $ref = htmlspecialchars($_GET['ref']);
   // On tente de récupérer le produit dans la base de données, à partir de sa référence
   $produit = $bdd->prepare('SELECT * FROM produits WHERE ref = ?');
   $produit->execute(array($ref));
   $produit = $produit->fetch(PDO::FETCH_ASSOC);
   // On vérifie que le produit existe bien (<=> qu'il a été trouvé dans la base de données)
   if ($produit) {
      $payer = new PayPalPayment();
      $payer->setSandboxMode(1);
      $payer->setClientID("AaNfFJXpjGvp_177S8RymCVKU_lpzfE004tyiICzEiPkoRAMS54PU4F79zeugNHUAfjsfuPUoGm3IqNX");
      $payer->setSecret("Votre Secret"); // On indique son Secret
      // On remplie les informations de paiement à partir de notre produit, récupéré plus haut dans $produit.
      $payment_data = [
         "intent" => "sale",
         "redirect_urls" => [
            "return_url" => "http://localhost/",
            "cancel_url" => "http://localhost/"
         ],
         "payer" => [
            "payment_method" => "paypal"
         ],
         "transactions" => [
            [
               "amount" => [
                  "total" => strval($produit["prix"]), // Prix total de la transaction, ici le prix de notre item. ATTENTION ! PayPal attend des prix de type chaîne de caractères (string), d'où le "strval()" de notre prix enregistré comme flottant en base de données.
                  "currency" => "EUR" // USD, CAD, etc.
               ],
               "item_list" => [
                  "items" => [
                     [
                        "sku" => $produit["ref"], // SKU (Stock Keeping Unit) correspond à la référence de notre item
                        "quantity" => "1", // On ne vend qu'un seul item
                        "name" => $produit["titre"], // Le titre de notre produit
                        "price" => strval($produit["prix"]), // Son prix, toujours au format chaîne de caractères
                        "currency" => "EUR"
                     ]
                  ]
               ],
               "description" => "Une super commande passée sur notre site !" // Description du paiement. S'il n'y a qu'un seul item, on pourrait éventuellement passer la description du produit ici, mais ça n'a pas grand intérêt.
            ]
         ]
      ];
      $paypal_response = $payer->createPayment($payment_data);
      $paypal_response = json_decode($paypal_response);
      if (!empty($paypal_response->id)) {
         // On oublie pas d'ajouter la référence de l'item dans le nouveau champ "produit" de notre table "paiements", afin de garder une trace du produit acheté !
         $insert = $bdd->prepare("INSERT INTO paiements (produit, payment_id, payment_status, payment_amount, payment_currency, payment_date, payer_email) VALUES (:produit, 😛ayment_id, 😛ayment_status, 😛ayment_amount, 😛ayment_currency, NOW(), '')");
         $insert_ok = $insert->execute(array(
               "produit" => $produit['ref'],
               "payment_id" => $paypal_response->id,
               "payment_status" => $paypal_response->state,
               "payment_amount" => $paypal_response->transactions[0]->amount->total,
               "payment_currency" => $paypal_response->transactions[0]->amount->currency
            ));
         if ($insert_ok) {
            $success = 1;
            $msg = "";
         }
      } else {
         $msg = "Une erreur est survenue durant la communication avec les serveurs de PayPal. Merci de bien vouloir réessayer ultérieurement.";
      }
   } else {
      $msg = "Le produit que vous tentez d'acheter n'est pas disponible.";
   }
}
echo json_encode(["success" => $success, "msg" => $msg, "paypal_response" => $paypal_response]);

Finalement, dans le script d’exécution du paiement, rien ne change, du moins pour l’exemple. En effet, celui-ci se charge juste de valider un paiement effectué auparavant, peu importe l’item lié à ce paiement !

Dans la pratique, ça reste un poil différent. En effet, suite à la confirmation du paiement effectué par un client, il pourrait être une bonne idée de lui envoyer un mail de confirmation, ainsi que le contenu de sa commande, etc. Tout cela se passera donc au niveau du script php/paypal_execute_payment.php, avant la ligne où nous avons choisi de passer la variable $success à 1 indiquant que tout s’est déroulé correctement. Bien sûr, encore une fois, libre à vous de rajouter des conditions, étapes, fonctionnalités, etc. selon vos besoins.

Test du système

On peut donc rapidement tester notre script, celui-ci fonctionnera de la même façon que précédemment. Cependant, les informations de notre produit apparaîtront directement dans la fenêtre de paiement PayPal en guise de récapitulatif, et la référence du produit acheté sera disponible dans le champ de notre table « paiements ». Voilà ce que ça donne en images :


A propos de l'auteur

PrimFX
Boris ('PrimFX')

Je m'appelle Boris, j'ai 22 ans et je suis passionné d'informatique. Suite à mes études (Licence Informatique puis MSc Computer Science au Trinity College Dublin), je gère l'entreprise Single Quote co-fondée en 2019 et je profite de mon temps libre pour partager ma passion à travers des vidéos & articles 😃