{Developpement} – Protocole « Concert » et paiement via un TPE

Bonjour à tous,

 

Dans mon métier, celui de "Concepteur Développeur", je suis régulièrement amené à toucher un petit peu à tous et sous tous les angles ... et c'est ce qui me plaît !
Beaucoup de retard dans mes publications, mais mon nouveau boulot me prends pas mal de temps et je dois dire que lorsque je rentre à la maison ... j'enfile ma cape de Super Papa et ma journée continue ... et lorsque je suis libéré de toute obligation d'un bon Mari et Père de famille ... je n'ai qu'une envie c'est de me poser et de ne plus rien faire.
Un sentiment qui parlera certainement à beaucoup d'entre vous !

Aujourd'hui, et pour compenser un peu le retard, j'aimerais vous proposer à la fois un bout de code NodeJS qui vous permettra d'envoyer de contrôler un terminal de paiement électronique (TPE), et vous expliquer un petit peu comment fonctionne le protocole "Concert" qui est généralisé sur un grand nombre de TPE.

 

Les drivers

La première chose à faire avant même de brancher votre TPE est de disposer des bons drivers.
Si vous êtes sous Linux, vous ne devriez pas avoir besoin de télécharger ou d'installer de drivers.
Cependant sous Windows, il vous faudra passer par la case "installation des drivers":
https://www.ingenico.fr/support/notices-utilisateurs/boite-a-outils

 

Protocole Concert

En partant du principe que vous avez disposez, et ou que vous avez installé les bons drivers et que votre TPE est branché à votre ordinateur ... nous allons pouvoir étudier un petit peu comment faire interagir votre programme avec le TPE en USB / PORT COM (Série).
Un petit paragraphe vous expliquera rapidement les changements mineure à réaliser si vous utilisez un TPE en réseau.

Identifier le port série

Si vous êtes sous Windows, vous devrez très probablement identifier quel est le port COM / Série qui est utilisé par votre TPE via le "gestionnaire de périphérique".
Si vous êtes sur Linux, il s'agira très très certainement de '/dev/ttyACM0'.

Vitesse de transmission

Là aussi, il vous faudra consulter selon votre modèle la meilleure vitesse de transmission.
Le plus souvent on utilise la valeur 9600

Le protocole

En admettant que vous ayez identifié le bon port série, et que vous ayez trouvé la bonne vitesse de transmission, nous allons pouvoir envoyer des instructions.

Pour cela vous devrez vous munir de la table ASCII

 

Par exemple si je vous demande d'envoyer la valeur ENQ, vous devrez envoyer la valeur décimale (DEC) qui est "5", pour ACK ça sera 6, etc ...

Commençons donc !

1. On envoie le signal ENQ (5)

2. Si l'on reçoit un ACK (6) on peut continuer, sinon on envoie un EOT et on réessaie (jusqu'à 3 fois)

3. On construit notre message, en concaténant dans cette ordre les valeurs suivante:

  1. pos_number : 2 caractères qui correspondant au numéro de la caisse (Ex : 01)
  2. amount_msg : 8 caractères correspondants au montant x 100, en préfixant par autant de 0 qu'il faut pour occuper les 8 caractères. (Ex: pour 0.1 centimes => 00000010)
  3. answer_flag: Indicateur de champ réponse, 1 caractère il me semble qu'il s'agit là de demander au TPE d'envoyer un peu plus d'infos sur la transaction. Pour ma part je la valorise à 0
  4. payment_mode : 1 caractère, 'C' s'il s'agit d'un paiement chèque, '1' s'il s'agit d'un paiement par carte bancaire
  5. transaction_type : 1 caractère, '0' pour un débit (demande de paiement), '1' pour un crédit (remboursement)
  6. currency_numeric : 3 caractères correspondant au "code" de la devise. 978 pour l'EURO (https://fr.iban.com/currency-codes)
  7. private: 10 caractères à utiliser à votre guise ... (par exemple pour sécuriser une transaction). Si vous n'en avez pas l’intérêt mettre 10 espaces ("          ")
  8. delay : "A010" pour une réponse du TPE au moment de la transaction (annulation ou réussite de l'opération, "A011" pour une réponse immédiate.
  9. auto: "B010" pour que le TPE propose de demander une autorisation.

Une fois que j'ai tout ça, je devrais avoir une chaine de caractère ressemblant à cela :
"0100000010010978          A010B010"
Assurez vous bien que vous ayez bien 34 caractères.

J'ajoute à la fin de cette chaine le caractère ASCII "ETX" (3)

Ensuite je génère un "octet de contrôle", que nous appellerons "LRC" en parcourant chaque caractère de cette chaîne de caractère et en faisant une "Affectation après OU exclusif binaire"

generate_lrc = function(real_msg_with_etx){
	let lrc = 0, 
		text = real_msg_with_etx.split('');
	for (let i in text){
		lrc ^= text[i].charCodeAt(0);
	}
	return lrc;
}

Voici donc le message que je vais envoyer au TPE :
caractère ascii STX + "0100000010010978          A010B010" + caractère ascii ETX + octer de contrôle lrc

 

4. Si le TPE a bien reçu ma demande de paiement, il me renverra le code ascii ACK

5. Si j'ai bien reçu le ACK, je revoie un EOT !

6. Le TPE vous affiche le montant à payer (ici ça sera 0.1 cts)

7 Lorsque le paiement est "annulé" ou "réalisé" vous allez recevoir là-aussi un bloc de données, qui correspondront dans cet ordre à ça :

'pos_number' : 2 caractères correspondant au numéro de caisse.
'transaction_result': 1 caractère (0 la transaction s'est bien passé, tous le autres chiffres signifie que la transaction a échouée, je mettrais le détails plus en dessous**),
'amount_msg' : 8 caractères (le montant de la transaction, cf envoie du message)
'payment_mode' : 1 caractère, correspond au moyen de paiement (Carte bancaire 1, Chèque C, Amex 2, CETELEM 3, Cofinoga 5, Diners 6, Franfinance 8, JCB 9, Accord-finances A, Moneo O, CUP U, FINTRAX EMV F, autres 0)
'currency_numeric' : La devise utilisée,
'private' : la même donnée qui a été envoyée

Exemple de données reçues:
"010000000101978          "

 

** Petit supplément d'information sur transaction_result :
Tout chiffre qui n'est pas 0 correspond à un echec du paiement :

  1. NOK : auth
  2. Problème de téléphonie
  3. Force ???
  4. Paiement refusé
  5. Carte interdite
  6. Annulation de la banque
  7. Transaction qui échoue
  8. Transaction impossible
  9. Erreur inconnue

Mes petits morceaux de code en NodeJS

Ci-dessous une script NodeJS qui vous permettra en mode "PoC" (proof of concept), l'envoi d'une demande de "paiement" à un TPE

const SerialPort = require('serialport')
//const Readline = require('@serialport/parser-readline')
/*
* 
*   Dave Hill | dave-hill@dyrk.org
*   USING PROTOCOL CONCERT
*   ref : https://lists.launchpad.net/openerp-community/pdfcezlBjgtdJ.pdf
*	NodeJS >= 8
*/

/*
 *
 *  syncSerialPort : init Serial Communication
 *  Take in params the value to send on TPE
 *  & an "Array" (Object) with the expected value binded with their function to run
 */
var syncSerialPort = function(config, req, listAction){
    const port = new SerialPort(config.DEVICE, {
        baudRate:config.DEVICE_RATE,
        parser: new SerialPort.parsers.Readline()
       });
    port.on('data', (function(config, listAction, line){
        config.lastReceive = line.toString();
        if (typeof listAction[line] == 'function')
          listAction[line](config);
        else if (listAction['default'])
           listAction['default'](config);
        else
          console.log(listAction, line);
        port.close();
    }).bind(null, config, listAction));
    port.on('open',(function(req){
         port.write(req);
         data = port.read();
    }).bind(null, req));
    port.on('error', function(err){});
    //Make Timeout (serial Port is Busy, process in while, ..., terrorist)
    setTimeout(()=>{ port.close(); }, 120000); // It's dirty & you can remove this ;)
    return new Promise(function(resolve){
        return port.on("close", resolve)
    });
  },
  /*
   *
   *    generate_lrc : used for the signature of message
   *
   *
   */
  generate_lrc = function(real_msg_with_etx){	
    	let lrc = 0, text = real_msg_with_etx.split('');
        for (i in text){
            lrc ^= text[i].charCodeAt(0);
        }
        console.log('lrc => ', lrc);
        return lrc;
    },  
    send_eot_signal = function(config){
        console.log('Signal EOT sent to terminal');
        listAction = {};
        listAction[String.fromCharCode(5)] = (function(config){
           console.log('ENQ received from terminal');
           send_enq_signal(config);
         }).bind(null, config); 
        syncSerialPort(config, String.fromCharCode(4),listAction); //EOT
    },
    send_enq_signal = function(config){
        console.log('Signal ACK sent to terminal');
        listAction = {};
        listAction['default'] = get_answer_from_terminal.bind(null, config)
        syncSerialPort(config, String.fromCharCode(6),listAction); //ACK
    },
    /*
     *
     *  get_answer_from_terminal : Get the confirmation of the Payement
     *  (or cancelation)
     *
     *
     */
    get_answer_from_terminal = function(config){
        let response = {};
        console.log('Now expecting answer from Terminal');
        config.lastReceive = config.lastReceive.
                             substr(1).substr(0, config.lastReceive.length-3);
        console.log(config.lastReceive);
        response = {
            'pos_number'        : config.lastReceive.substr(0, 2),
            'transaction_result': config.lastReceive.charAt(2),
            'amount_msg'        : config.lastReceive.substr(3, 8),
            'payment_mode'      : config.lastReceive.charAt(11),
            'currency_numeric'  : config.lastReceive.substr(12, 3),
            'private'           : config.lastReceive.substr(15, 11)
        };
        console.log('response : ', JSON.stringify(response));
    },
    /*
     *
     *  Prepare & Send Payment Request to TPE
     *  !!!! DON'T CHANGE THE ORDER !!!
     *
     */
    send_message    = function(config){
        console.log('Send Message  ...');
        let data = {
            'pos_number': '01',
            'amount_msg': ('0000000' + (config.AMOUNT * 100).toFixed(0)).substr(-8),
            'answer_flag': '0',
            'payment_mode': config.PAYMENT_MODE  == 'check' ? 'C' : '1', 
            'transaction_type': '0',
            'currency_numeric': 978, 
            'private': '          ',
            'delay': 'A010',
            'auto': 'B010'
        };
        msg = Object.keys(data).map( k => data[k] ).join('');
        if (msg.length > 34) return console.log('ERR. : failed data > 34 characters.', msg);
        real_msg_with_etx = msg.concat(String.fromCharCode(3));//ETX
        lrc = generate_lrc(real_msg_with_etx);
        //STX + msg + lrc
        tpe_msg = (String.fromCharCode(2)).concat(real_msg_with_etx).concat(String.fromCharCode(lrc));
        console.log('Real message to send =', msg);
        console.log('Message sent to terminal');
        listAction = {};
        listAction[String.fromCharCode(6)] = ((config)=>{ 
          console.log('ACK received from terminal'); 
          send_eot_signal(config);
        }).bind(null, config);
             syncSerialPort(config, tpe_msg, listAction);
    },
    /*
     *
     *  Init the TPE
     *
     *
     */
    transaction_start = function(config){
        try {
            // Init
           console.log('Signal ENQ sent to terminal');
           listAction = {};
           listAction[String.fromCharCode(6)] = (function(config){
               console.log('ACK received from terminal');
               send_message(config);
           }).bind(null, config);
           syncSerialPort(config, String.fromCharCode(5),listAction);
         } catch (e){
            console.log(e);            
         }
    };




/*
 *
 *  Main - Poc 
 *
 *
 *
*/
var config = {
    DEVICE : '/dev/ttyACM0',
    DEVICE_RATE : 9600,
    PAYMENT_MODE : 'card',
    AMOUNT : 0.01
}

transaction_start(config);

Ci-joint une archive qui vous permettra de monter un mini-serveur (développé en Node >= 8) qui pilotera le TPE qu'il soit en réseau ou en USB / COM  : gestion-tpe.tar

 

Petite vidéo de démonstration du PoC

Désolé pour la "tremblote", je voulais faire une démo avec une vue d'ensemble TPE et ordinateur ... mais je ne suis pas hyper équipé  ( ni un très bon monteur vidéo :D )

 

 

Quelques Changements pour les TPE en réseau

Si vous disposez d'un TPE qui est connecté directement sur votre box et qui dispose d'une IP.
Vous pouvez échanger directement via le port 8888
Inutile de jongler avec des envois de signaux comme pour l'USB / Port série, ici on envoie directement une demande de paiement / remboursement et on reçois directement la réponse.

Petite précision qui a son importance :
Le "pos_number " ne fera qu'un seul caractère dans vos requêtes ou dans les réponses à vos requêtes.

 

Conclusion

Je vous remercie encore une fois d'avoir pris le temps de lire cet article, mais également de votre fidélité qui comme chaque fois me va droit au cœur.

Dans l'étude que j'ai menée pour l'écriture je n'ai trouvé quasiment aucune documentation sur la partie "Serial Port" (j'ai dit "quasiment", ça ne veut pas dire qu'il n'y en a pas ^^), et aucune sur la partie "réseau" ... J'espère que cette modeste pierre à l'édifice vous permettra dans vos projets "pro" ou "perso" de mieux aborder ce sujet.
D'ailleurs si vous avez des informations complémentaires, ou si je dis n'importe quoi n'hésitez pas à me le dire en commentaire.
Je suis également disponible sur Facebook et  sur Youtube (profitez-en pour vous abonner ;) )

Je projette également de vous expliquer dans un prochain article le "Hack des TPE" ;)

Bonne soirée à tous.

Partagez ce contenu

7 comments

  • bonjour,

    il y a un problème avec la vidéo d’après youtube elle est en privé.

    bonne journée

  • Très instructif  cette article et comme tout le site d’ailleurs

    PS: je t’ai laisser un mail sur ta boîte ?

  • Salut tout le monde j’espère qu’il y a un hacker qui m’aide je suis victime d’un hacker qui m’a pirater mon facebook parce que je suis gamer il m’a volé mon compte de gaming s’il vous plaît aidez-moi à récupérer mon compte

  • Bonjour,

    Pour annuler un ordre en cours, quand sur l’ecran du tpe le montant est déjà écrit.

    il y a t’il un ordre a envoyer au tpe pour revenir sur l’écran « Debit 0,00Eur » ou la seule possibilité c’est l’appui sur le bouton rouge du tpe.

  • Bonjour

    Merci pour ton code très utile car je suis entrain de travailler sur un projet de transmission de montant à un TPE ingenico Desk5000 suivant le même protocole en Python mais j’ai des difficultés car le TPE ne reconnait pas mes trames envoyés en ASCII.

    Comment dois-je les coder ?

    Bonne journée

     

  • Bonjour,

    Le tutoriel est super bien fait, je suis bloquer sur une étape du Protocol concert, je ne c’est pas comment mit prendre.

    Je travaille actuelle sur des terminales bancaire de type desk/5000.

    Je suis bloqué est à l’étape 7 du tutoriel (7 Lorsque le paiement est « annulé » ou « réalisé » vous allez recevoir…) , le montant affiche bien sur le TPE, mais je sais pas comment récupérer le bloc de données sur le port COM.

    Je reçois comme réponse du TPE 05 84 05 84 05 84.

    la question est la suivante. comment gardé l’écoute du port COM du TPE vers le PC ?

    Cordialement,

  • Bonjour,

    J’essaye d’utiliser le code en Ethernet, mais je n’ai pas l’impression que le tpe écoute. Un telnet 8888 du tpe me dit connexion refusée. Si je cherche les ports ouverts, (avec nmap) il n’en trouve aucun.

    Est-ce qu’il y a une configuration particulière à faire sur le tpe?

    Merci d’avance

    Thomas

Laisser une réponse

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