Poesía Binaria

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

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:

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:

¡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:

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....