Publi

Cómo instalar o migrar el TPV de Redsys en tu web (nueva versión SHA256)

negativespace1-21

Hoy vamos a hablar de Redsys, una plataforma de comercio electrónico seguro que cubre el proceso completo de compra online, desde el acceso a la página web del comercio, hasta el proceso de autorización del pago, previa autentificación del cliente.

¡ Este post es una colaboración ! Y debemos agradecérselo a

Cristina Soler. (@cso1992). Desarrolladora de aplicaciones web, solucionadora de problemas (hace maravillas con PHP y Javascript) y amante de los animales

Y mi primera colaboración en el blog con un post que seguro que a más de uno le interesa, la reciente migración de SHA1 a SHA256 en los TPV de Redsys. Y si no estás migrando, cómo implementarlo

Hasta ahora, toda comunicación entre una tienda web y el TPV Virtual de Redsys ha ido firmada electrónicamente utilizando un algoritmo de “hashing” conocido como SHA1, el cual ha sido declarado como obsoleto al poder ser atacado dada su poca seguridad. Es por esto que, a lo largo de estos últimos meses, a todos los propietarios de un comercio electrónico les ha llegado una notificación para que migren sus sistemas de pago al nuevo método: conexión con firma SHA256. Sin dicha migración, este sistema de pago dejará de funcionar en todos los comercios a partir del 23 de Noviembre de 2015 (hoy).

Aunque existe documentación propia de Redsys al respecto, yo que la he seguido en sus dos primeras versiones, he visto algunas carencias en la misma que yo os comentaré en este post y creo que serán de gran utilidad, ya que yo me tiré un buen rato para encontrar la solución a mis problemas.

¿Cómo funciona Redsys?

Cuando un usuario va a realizar una compra en un comercio online con pago Redsys, el comercio redirige la sesión del navegador del usuario, dejando que el TPV Virtual tome todo el control. Para que esta redirección sea correcta, debemos preparar en nuestro comercio un formulario oculto como el siguiente:

1
2
3
4
5
<form action="https://sis.redsys.es/sis/realizarPago" method="post" id="redsys_form" name="redsys_form">
    <input type="text" name="Ds_SignatureVersion" value="HMAC_SHA256_V1" hidden />
    <input type="text" name="Ds_MerchantParameters" value="' . $json . '" hidden />
    <input type="text" name="Ds_Signature" value="' . $signature . '" hidden />
</form>

Si nos fijamos, este formulario es mucho más simple que el que mandábamos con el método anterior que era algo como:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<input type="hidden" name="Ds_Merchant_Amount" value="'.$amount.'" />
<input type="hidden" name="Ds_Merchant_Currency" value="'.$moneda.'" />
<input type="hidden" name="Ds_Merchant_Order" value="'.$id.'" />
<input type="hidden" name="Ds_Merchant_MerchantCode" value="'.$codigo.'" />
<input type="hidden" name="Ds_Merchant_Terminal" value="'.$terminal.'" />
<input type="hidden" name="Ds_Merchant_TransactionType" value="'.$trans.'" />
<input type="hidden" name="Ds_Merchant_Titular" value="'.$titular.'" />
<input type="hidden" name="Ds_Merchant_MerchantName" value="'.$nombre.'" />
<input type="hidden" name="Ds_Merchant_MerchantData" value="'.sha1($urltienda).'" />
<input type="hidden" name="Ds_Merchant_MerchantURL" value="'.$urltienda.'" />
<input type="hidden" name="Ds_Merchant_ProductDescription" value="'.$desc.'" />
<input type="hidden" name="Ds_Merchant_UrlOK" value="'.$urltienda.'" />
<input type="hidden" name="Ds_Merchant_UrlKO" value="'.$urltienda.'" />
<input type="hidden" name="Ds_Merchant_MerchantSignature" value="'.$firma.'" />
<input type="hidden" name="Ds_Merchant_ConsumerLanguage" value="'.$idiomaFinal.'" />
<input type="hidden" name="Ds_Merchant_PayMethods" value="'.$tipopago.'" />

En lugar de mandar toda esa cantidad de parámetros por separado, ahora debemos mandar solo tres:

  • Ds_SignatureVersion: indica la versión de firma utilizada. Con esta nueva firma, esta versión será: HMAC_SHA256_V1.
  • Ds_MerchantParameters: es en este parámetro en el que agruparemos gran parte de los parámetros que en el método anterior mandábamos por separado. Se trata de una cadena en formato json, de modo que quedará algo como esto:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?php
    $data = array(
            'Ds_Merchant_Amount' => $amount,
            'Ds_Merchant_Currency' => $currency,
            'Ds_Merchant_Order' => strval($orderId),
            'Ds_Merchant_MerchantCode' => $code,
            'Ds_Merchant_Terminal' => $terminal,
            'Ds_Merchant_TransactionType' => $trans,
            'Ds_Merchant_Titular' => $titular,
            'Ds_Merchant_MerchantName' => $name,
            'Ds_Merchant_MerchantURL' => $url, // url a "redsys/index/notify"
            'Ds_Merchant_MerchantData' => sha1($url),
            'Ds_Merchant_ProductDescription' => $description,
            'Ds_Merchant_UrlOK' => $url,
            'Ds_Merchant_UrlKO' => $url,
            'Ds_Merchant_ConsumerLanguage' => $language
        );

    Además, este json deberemos codificarlo en base64 y sin retornos de carro:

    1
    2
    3
    4
    5
    6
    <?php
    public function encodeBase64($data){
            $data = base64_encode($data);
            return $data;
        }
        $json = $this->encodeBase64( json_encode($data) );
  • Ds_Signature: se trata de la firma de los datos enviados y es el resultado del hmac_sha256 del json del parámetro anterior.

Llegados a este punto, debemos acceder al panel de administración de nuestro Redsys para obtener una nueva clave de comercio, compuesta por 32 caracteres, que debemos almacenar en el admin de nuestro cms y que almacenaremos en la variable $kc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
$signature = $this->createMerchantSignature($kc, $json, $orderId);
   
    public function createMerchantSignature($key, $ent, $order){
        // Se decodifica la clave Base64
        $key = $this->decodeBase64($key);
        // Se diversifica la clave con el Número de Pedido
        $key = $this->encrypt_3DES($order, $key);
        // MAC256 del parámetro Ds_MerchantParameters
        $res = $this->mac256($ent, $key);
        // Se codifican los datos Base64
        return $this->encodeBase64($res);
    }

    public function encodeBase64($data){
        $data = base64_encode($data);
        return $data;
    }

    public function mac256($ent,$key){
        $res = hash_hmac('sha256', $ent, $key, true);//(PHP 5 >= 5.1.2)
        return $res;
    }

    public function decodeBase64($data){
        $data = base64_decode($data);
        return $data;
    }

    public function encrypt_3DES($message, $key){
        // Se establece un IV por defecto
        $bytes = array(0,0,0,0,0,0,0,0); //byte [] IV = {0, 0, 0, 0, 0, 0, 0, 0}
        $iv = implode(array_map("chr", $bytes)); //PHP 4 >= 4.0.2

        // Se cifra
        $ciphertext = mcrypt_encrypt(MCRYPT_3DES, $key, $message, MCRYPT_MODE_CBC, $iv); //PHP 4 >= 4.0.2
        return $ciphertext;
    }

Una vez tenemos todos estos parámetros definidos, debemos hacer una comprobación de firmas para asegurarnos que el pago no ha sido manipulado.

Tras esto, entraremos en el POST ($this→getRequest()→isPost()) de la función notifyAction() del código de Redsys en nuestro comercio. Ahí, recogeremos los datos recibidos por POST y ocalculamos la firma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
    $params = $_POST['Ds_MerchantParameters'];
    $decoded = $this->decodeMerchantParameters($params);
    $orderId    = $decoded["Ds_Order"];
    $signature = $_POST['Ds_Signature'];
    $firma = $this->createMerchantSignatureNotif($kc, $params, $orderId);

    public function createMerchantSignatureNotif($key, $datos, $pedido){
        // Se decodifica la clave Base64
        $key = $this->decodeBase64($key);
        // Se diversifica la clave con el Número de Pedido
        $key = $this->encrypt_3DES($pedido, $key);
        // MAC256 del parámetro Ds_Parameters que envía Redsys
        $res = $this->mac256($datos, $key);
        // Se codifican los datos Base64
        return $this->base64_url_encode($res); 
    }

    public function decodeMerchantParameters($datos){
        // Se decodifican los datos Base64
        $decodec = $this->base64_url_decode($datos);
        //return $decodec; 
        return json_decode($decodec, true);
    }

    public function base64_url_encode($input){
        return strtr(base64_encode($input), '+/', '-_');
    }

    public function base64_url_decode($input){
        return base64_decode(strtr($input, '-_', '+/'));
    }

Una vez calculada la firma con los datos recibidos, debemos validar el parámetro Ds_Signature. Para ello, comparamos la firma calculada con la firma que previamente hemos enviado. Si no coinciden, se cancelará el pago puesto que se trata de un error y podría tratarse de una manipulación de datos.

Si por el contrario, ambas firmas coinciden, será señal de que todo va bien y el usuario será devuelto a la web del comercio.

1
2
3
4
5
6
<?php
if ($signature === $firma) {
    // Las firmas coinciden. Hacer algo en el servidor aquí, como enviar correo del pedido al comprador/vendedor y actualizar el estado del pedido a “En proceso”
} else {
    // Las firmas no coinciden. Actualizar el estado del pedido a “Cancelado”
}

En este punto, estaríamos en el GET ($this->getRequest()->isGet()) de la función notifyAction() de la que hablabamos antes. Del mismo modo que hicimos antes, volvemos a validar los datos de vuelta:

1
2
3
4
5
6
<?php
    $params = $_GET['Ds_MerchantParameters'];
    $decoded = $this->decodeMerchantParameters($params);
    $orderId    = $decoded["Ds_Order"];
    $signature = $_GET['Ds_Signature'];
    $firma = $this->createMerchantSignatureNotif($kc, $params, $orderId);

Del mismo modo, comprobamos de nuevo las dos firmas: la calculada según los datos recibidos y la que obtuvimos del Admin de Redsys.

1
2
3
4
5
6
<?php
if ($signature === $firma) {
    // Las firmas coinciden. Redirigir al usuario a la página de “Compra finalizada”.
} else {
    // Las firmas no coinciden. Redirigir al usuario a la página de “Compra fallida”.
}

Bien, si ahora simulamos una compra en nuestro comercio, veremos que el TPV se muestra correctamente de la siguiente forma:
tpv_redsys

¡Ups! ¿Pagar con iupay? ¿Qué es eso?

Iupay es una cartera digital bancaria, se trata de un servicio que se ofrece a los comercios online que permiten a los clientes pagar usando sus tarjetas bancarias enroladas en el servicio, sin necesidad de hacer lo que tanto les cuesta a muchos: introducir sus datos bancarios en Internet.

Pero… ¿Qué pasa si quiero deshabilitar el pago por iupay? ¿Cómo lo hago?
Bien, aquí viene la pregunta que no responde la documentación de Redsys, ya que en listado de datos válidos en el parámetro Ds_Merchant_Parameters no nos dice en ningún momento que se le pueda pasar el parámetro Ds_Merchant_PayMethods que en el método SHA1 de Redsys sí que pasábamos.
Es por ello que, cuando seguí la documentación de Redsys para migrar un comercio online a SHA256, me volví loca hasta que me dí cuenta de la falta y necesidad de Ds_Merchant_PayMethods.

Pues bien, una vez sabido esto, es fácil deshabilitar el pago por iupay de Redsys. Basta con modificar el array $data que declaramos al principio de este post. Tras ello, el array será el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    $data = array(
            'Ds_Merchant_Amount' => $amount,
            'Ds_Merchant_Currency' => $moneda,
            'Ds_Merchant_Order' => strval($id),
            'Ds_Merchant_MerchantCode' => $codigo,
            'Ds_Merchant_Terminal' => $terminal,
            'Ds_Merchant_TransactionType' => $trans,
            'Ds_Merchant_Titular' => $titular,
            'Ds_Merchant_MerchantName' => $nombre,
            'Ds_Merchant_MerchantData' => sha1($urltienda),
            'Ds_Merchant_MerchantURL' => $urltienda,
            'Ds_Merchant_ProductDescription' => $desc,
            'Ds_Merchant_UrlOK' => $urltienda,
            'Ds_Merchant_UrlKO' => $urltienda,
            'Ds_Merchant_ConsumerLanguage' => $idiomaFinal,
            'Ds_Merchant_PayMethods' => $tipopago
        );

Donde $tipopago puede obtener los siguientes valores:

  • C: Sólo Tarjeta (mostrará sólo el formulario para datos de tarjeta)
  • R: Pago por Transferencia (solo, si tiene activo este método de pago)
  • D: Domiciliacion (solo, si tiene activo este método de pago)
  • T: Tarjeta + iupay (mostrará el formulario de tarjeta y además el botón iupay)

Por lo tanto, si queremos desactivar el pago iupay, basta con no darle el valor “T” a $tipopago.

Si habéis configurado todo como es debido, tras ingresar los datos correspondientes en el formulario de pago, lograreis finalizar la compra exitosamente.

NOTA: Todas las funciones PHP que aparecen en este post pertenecen a las librerías de ayuda de Redsys, aunque con algunos pequeños cambios.

Foto: Negative Space

También podría interesarte....

There are 14 comments left Ir a comentario

  1. Pingback: Cómo instalar o migrar el TPV de Redsys en tu web (nueva versión SHA256) | PlanetaLibre /

  2. Miguel /
    Usando Safari Safari 601.1 en iOS iOS 9.1

    Excelente y completo artículo que de seguro a más de uno le va a ser de gran utilidad

  3. Joaquín /
    Usando Google Chrome Google Chrome 46.0.2490.86 en Windows Windows XP

    Me gustaría ponerlo en mi tienda . Todos ese código va en un solo php? …. Creo que una parte iría en mi página y la otra en un archivo externo al que llama el formulario.

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 42.0 en Ubuntu Linux Ubuntu Linux

      Por un lado tienes que generar el formulario, y luego cuando se realice el pago Redsys contacta con nosotros directamente (algo así como por detrás) para infomarnos de cómo ha ido la cosa.

      Es decir, por un lado tendremos el php que genera el formulario y por otro lado tendremos que verificar la información que nos llega de redsys, nos llamarán desde otra URL, pero lo mismo podemos crear un php común para las dos cosas, o uno para cada, eso ya es cosa nuestra.

      1. Joaquín /
        Usando Google Chrome Google Chrome 46.0.2490.86 en Windows Windows XP

        Gracias por contestar…Ya lo tengo casi listo….Me falta pasar el precio de la compra a la pasarla de pago…osea ná. Gracias 1000 por tu ayuda.

  4. Esther /
    Usando Google Chrome Google Chrome 47.0.2526.73 en Windows Windows NT

    Este artículo está genialmente explicado. Pero en mi caso, el parámetro “Ds_Merchant_PayMethod” no funciona. Lo que estoy intentando es que se vaya directamente a IUPAY, en la documentación antigua pone que hay que especificar el valor “O” (letra o mayúscula), pero ni caso. En la documentación antigua el nombre del parámetro está en singular, pero lo he probado de las dos maneras y nada.

    ¿Alguien más se ha encontrado en este caso?

    PD: No he encontrado la documentación nueva, la general. Sólo encuentro para determinadas plataformas (Prestashop, Magento, etc). Pero en mi caso trabajo con ASP/VBScript.

    1. José /
      Usando Google Chrome Google Chrome 48.0.2564.109 en Windows Windows 7

      Hola

      Para los que aún utilizan ASP clásico, aquí tenéis la implementación del API de RedSys para ASP: https://github.com/ictmanagement/redsysHMAC256_API_ASP

      Espero que os sea de utilidad

      1. Gaspar Fernández / Post Author
        Usando Mozilla Firefox Mozilla Firefox 44.0 en Ubuntu Linux Ubuntu Linux

        Gracias por tu comentario !

  5. Jack /
    Usando Google Chrome Google Chrome 47.0.2526.106 en Windows Windows NT

    Me gustaria que me ayudaráis a migrar el sha1 a sha256 para Symfony.
    Gracias!

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 42.0 en Ubuntu Linux Ubuntu Linux

      ¿ Qué problema tienes ?

  6. Pingback: Un buen 2015 para el blog. Los posts que han marcado el año y consultas SQL – Poesía Binaria /

  7. Andreu /
    Usando Google Chrome Google Chrome 47.0.2526.111 en Mac OS X Mac OS X 10.10.5

    Hola,

    He implementado la migración y a priori todo funciona correctamente. El problema que tengo es que los pagos denegados me llegan como correctos. Es decir, que las firmas coinciden, y entonces la web los marca como correctos. Es normal que las firmas coincidan cuando el pago ha sido denegado por Redsys? Cómo puedo detectar si el pago ha sido denegado si las firmas me coinciden?

    Gracias

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 43.0 en Ubuntu Linux Ubuntu Linux

      Yo creo que en el post ha faltado incluir esa verificación.

      Que las firmas coincidan quiere decir que la comunicación entre nuestra web y el servidor de Redsys ha sido buena, y nadie lo ha interceptado. Es decir, si la transacción es aceptada, el “aceptado” nos ha llegado bien, y si es denegada, dicha respuesta también nos ha llegado bien.

      El caso es que tenemos que verificar el argumento $_POST[“Ds_Response”]. Cuando dicho argumento es menor de 101 la cosa ha ido bien. A partir de 101 empiezan las respuestas de “Tarjeta caducada”, “No se puede comunicar con la entidad”, “PIN rechazado” y demás.

  8. Carazo /
    Usando Google Chrome Google Chrome 46.0.2490.80 en Linux Linux

    Gran artículo, me alegra ver que en español hay contenido de tanta calidad técnica en un blog, por desgracia no es lo frecuente.

    Sólo apuntar que si usas algún sistema gestor de contenidos como WordPress, y WooCommerce en este caso al tratarse de ecommerce, existen plugins que ya te dan la funcionalidad y se incluyen como pasarelas de pago dentro del entorno.

    Un ejemplo: http://codection.com/producto/redsys-gateway-for-woocommerce

Leave a Reply