Crear y validar números de longitud fija con algoritmo de Luhn

algoritmodeluhn

Además de cómo revisar que estén bien los números generados con su dígito verificador con el mismo algoritmo. En ocasiones requerimos generar números para algún proyecto donde la longitud deba ser igual, por ejemplo para un inventario, para un listado de productos, para generar alguna tarjeta de lealtad/puntos o para lo que queramos pero además debería haber consistencia en los números a generar con un dígito verificador y poder comprobar que sean válidos.

El algoritmo de Luhn o fórmula de Luhn, también conocida como "algoritmo de módulo 10", es una fórmula de suma de verificación, utilizada para validar una diversidad de números de identificación; como números de tarjetas de crédito, números IMEI, etc. - Wikipedia

En nuestro ejemplo, vamos a generar números de 8 dígitos en total para una tarjeta de puntos de compras en tar.mx, donde cada número será único y tendrá su código verificador, además le generaremos códigos de barras para imprimir en las tarjetas. Necesitamos GNU barcode - como generar código de barras instalado y PHP, pero en cualquier versión de Linux por lo general ya los trae instalados y pre configurados, así que no deberíamos tener problema para tener un entorno así.

Digamos que vamos a almacenar en una tabla un id incrementado (auto_increment) como llave para nuestros clientes, en este ejemplo vamos a utilizar hasta 5 dígitos (2 serán usados para un identificador de producto y uno será el dígito verificador, con el cual nos dará los 8 en total). De los 5 dígitos podremos entonces tener hasta 99,999 noventa y nueve mil novecientos noventa y nueve registros. Si por nuestras necesidades requerimos más, será cosa de incrementar el total de longitud de la tarjeta final.

<?php
   /* generador de dígito verificador @ToRo 2016 https://tar.mx */
   function digito($digito) {
      $a=2;
      $sum = [];
      for($i=strlen($digito)-1;$i>=0;$i--) {
         $d =$digito[$i];
         //si es el primero es *2, de lo contrario *1;
         if($a<1) $a=2;
         $sum[] = $d*$a;
         $a--;
      }
      //ahora sumamos
      $total = 0;
      foreach($sum AS $d) {
         if(strlen($d)==1) $total += $d;
         else {
            $da = str_split($d);
            foreach($da AS $one) { $total += $one; }
         }
      }
      $total %= 10;
      if($total != 0) $total = 10-$total;
      return $total;
   }

Aquí tenemos la primera función que nos va a generar el dígito verificador. Si tenemos nuestros id: 1, 2 , 3 , 435, 50,230 entonces al generar su dígito verificador y poniendo hasta 5 dígitos como longitud fija, podría ser así:

<?php
   /* generar números de tarjeta de 8 dígitos*/
   $longitud=5; // longitud del id interno
   $numeros = [1,2,3,435,50230]; // id de mis clientes, podría ser obtenido de una DB
   $prefijo = "01"; //producto 01, se tiene de 1 a 99 tipo de tarjetas o productos
   foreach($numeros AS $k) {
      $numero = $prefijo;
      $numero .= str_pad($k,$longitud,0,STR_PAD_LEFT); // 1 = 000001, etc.
      $digito = digito($numero); //del prefijo + número
      $tarjeta = $numero.$digito;
      echo "Prefijo + ID : $numero, DV: ".$digito.", tarjeta: ".$tarjeta."\n";
   }
   //lo anterior, daría como salida:
/*
Prefijo + ID : 0100001, DV: 7, tarjeta: 01000017
Prefijo + ID : 0100002, DV: 5, tarjeta: 01000025
Prefijo + ID : 0100003, DV: 3, tarjeta: 01000033
Prefijo + ID : 0100435, DV: 7, tarjeta: 01004357
Prefijo + ID : 0150230, DV: 1, tarjeta: 01502301
*/

Listo, ya tenemos los números de tarjeta de 1,2,3, 435 y 50230 convertidos a los 8 dígitos, con el prefijo de producto/servicio como 01 y su dígito verificador. Este dígito verificador es requisito para  APIs o servicios con algunas empresas en México (OXXO por ejemplo). Ahora faltaría crear los códigos de barras, algo rápido sería así:

<?php
   $tarjeta = "01000017"; // (si nuestra tarjeta fueran esos 8 códigos)
   $tmpf = "/tmp/".$tarjeta.".ps"; //archivo temporal
   $tmpi = "/tmp/".$tarjeta.".png"; //archivo final
   $cmd = "barcode -e 128 -E -b $tarjeta -o $tmpf"; //archivo postscript
   $cmd = `$cmd`;
   //
   $cmd = "convert -density 300 $tmpf $tmpi"; //archivo png
   $cmd = `$cmd`;
   unlink($tmpf);

Lo cual nos generaría un archivo como este:

imagen
Listo, lo haríamos con cada uno de nuestros códigos generados y ya los podríamos añadir a la impresión de nuestra tarjeta ficticia. Dicho código se puede leer con cualquier pistola estándar (que de hecho solo son teclados) o bien utilizar como identificación para nuestra tarjeta de puntos que queremos. La utilidad de esto no tiene límites salvo la imaginación xD.

Por último la función para verificar si los números generados son adecuados según su dígito verificador:

<?php
   /* verifica número según el algoritmo de Luhn */
   function verifica($numero) {
      $numero_checksum = '';
      foreach (str_split(strrev((string) $numero)) as $i => $d) {
         $numero_checksum .= $i %2 !== 0 ? $d * 2 : $d;
      }
      return array_sum(str_split($numero_checksum)) % 10 === 0;
   }
   // utilizamos según nuestros números anteriores:
   $tarjeta = "01000017";
   echo "Tarjeta $tarjeta es válida: "; 
   echo verifica($tarjeta);
   echo "\n";
   //nos debería dar un resultado así: Tarjeta 01000017 es válida: 1

En caso de que no fuera válido no regresaría nada.

Eso es todo, el código fuente del script está disponible en mi github.

Actualización marzo 2017: añadimos ahí mismo un ejemplo para hacerlo con Swift (si, el de )

¡gracias Hans Peter Luhn! :D

Software, PHP, Código de barras, Swift

por Jorge Martínez Mauricio :)

¿Algo que comentar?


Suscríbete por correo electrónico, recibirás los nuevos escritos antes que nadie y es gratis 😊

¿Ya conoces los foros de tar?

Populares estos días

    tar.mx es un blog sobre tecnología y otras chunches