Poesía Binaria

Implementar un TPV básico con la pasarela Pasat Internet de 4b en PHP

Es una pasarela de pago muy sencilla de implementar, por ejemplo es la opción que tenemos si somos del Banco Santander; aunque en su documentación que es bastante extensa (más de 70 páginas) hay cosas que no dejan muy claras, y lo peor de todo, no hay ningún ejemplo de pasarela básica con PHP (y los ejemplo que indican sólo funcionan en plataformas Windows).

Por otra parte, la comunicación entre nuestra web y la pasarela se hace en texto plano, hubiera preferido algo como XML y que hubiera que firmar las comunicaciones, aunque sean entre servidores… pero bueno.

El funcionamiento es sencillo:

Mientras estamos desarrollando la aplicación, disponemos de un modo simulación, donde nos dan un número de tarjeta válido (sólo para la simulación), con su clave y su caducidad. Para diferenciar el modo simulación del modo real lo haremos a través de la URL a la que pediremos los datos.

Vamos por partes:

Decirle al TPV que queremos pagar

Para ello tenemos que hacer una petición POST a https://tpv2.4b.es/simulador/teargral.exe (si es el modo simulación) o https://tpv.4b.es/tpvv/teargral.exe si estamos en producción. En la petición POST debemos indicar referencia del pedido (order), codigo de tienda [PI00….] (store), idioma de la pasarela (idioma). Por ejemplo, para hacernos una idea de cómo podemos hacerlo con un pequeño formulario que sólo muestre un botón de acceso a la pasarela:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
   $ref=12345;
   $url='https://tpv2.4b.es/simulador/teargral.exe';
   $store='PI00....';
   $idioma='es';
?>
<form action="<?php echo $url; ?>" method="post">
<input type="hidden" name="order" value="<?php echo $ref; ?>" />
<input type="hidden" name="store" value="<?php echo $store; ?>" />
<input type="hidden" name="idioma" value="<?php echo $idioma; ?>" />
<input type="submit" name="enviar" value="Acceder a la pasarela de pago" />
</form>

En el campo store, para que no haya repeticiones, podemos incluir el número de factura, o podemos crear una cadena con todos los datos y pasarle un algoritmo de hash tipo MD5. Bueno, la pregunta del millón, ¿cómo es que tanto order como store están en inglés y luego encontramos un campo idioma ? Podía ser language, por ejemplo…

Desglose de la factura

Cuando el usuario accede al TPV, éste, por detrás, nos pide que le enviemos el desglose de la factura, el precio y alguna cosa más, por lo que el usuario nunca verá esta información, y nosotros deberíamos poder acceder a esta información en cualquier momento gracias al número del pedido (order), por lo que el TPV nos pasará como parámetro este código. Nosotros recibiremos por GET:

Luego nosotros tenemos que enviar el código de moneda (978 para el euro, según la norma ISO 4217), el precio en céntimos, el número de elementos del carrito, y por cada elemento del carrito, diremos su referencia, descripción, cantidad y precio todo esto a un elemento por línea (menos la moneda y el precio total que irán en la misma línea. En PHP podemos poner esto como ejemplo:

Por lo tanto:

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
39
40
41
42
43
44
45
46
47
48
<?php
define ('CRLF', "\r\n"); // Debemos utilizar esto como salto de línea (obligatorio)

function calcula_total($carrito)
{
   $total=0;
   for ($i=0; $i<count($carrito); $i++)
   {
      $total+=$carrito[$i]['Precio']*$carrito[$i]['Cantidad'];
   }

   return $total;
}

$store = (isset($_GET['store']))?$_GET['store']:false;
$order = (isset($_GET['order']))?$_GET['order']:false;

if (!$store || !$order)
  throw new Exception ("Datos insuficientes."); // Error, no hemos recibido uno de los dos datos

if ($store!='PI00...')
  throw new Exception ("Código de tienda erróneo"); // Error, no me han pasado el código de tienda bien

// Aquí debemos implementar un método de acceso al pedido, esto es sólo un ejemplo
if ($order!=12345)
  throw new Exception ("Pedido no valido"); // Error, el código de pedido no es válido

$carrito = array (
                  array('Ref' => 123,
                        'Descripcion' => 'Camiseta de Angry Birds',
                        'Precio' => 12,
                        'Cantidad' => 2
                       ),
                  array('Ref' => 124,
                        'Descripcion' => 'Gorra de Tux',
                        'Precio' => 6,
                        'Cantidad' => 1
                       )
                 );
$total = calcula_total($carrito) * 100; // Multiplicamos por 100 porque son céntimos

echo "M978".$total.CRLF;
echo count($carrito).CRLF;  // Ponemos el número de elementos del carrito
for ($i=0; $i<count($carrito); $i++)
{
   echo $carrito[$i]['Ref'].CRLF.$carrito[$i]['Descripcion'].CRLF.$carrito[$i]['Cantidad'].CRLF;
   echo ($carrito[$i]['Precio']*100).CRLF;
}

El TPV nos dice si la transacción es aceptada o denegada

De la misma forma que nos solicitó el desglose, el servidor nos avisará de la aceptación o denegación del pago, por lo que tenemos que utilizar este paso para conocer realmente el estado del pago, ya que este dato no pasa por el usuario, es comunicación entre servidores.
El TPV nos enviará (por GET):

Por lo tanto:

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
define('OPERACION_ACEPTADA', 0);
define('OPERACION_DENEGADA', 2);

$store = (isset($_GET['store']))?$_GET['store']:false;
$order = (isset($_GET['pszPurchorderNum']))?$_GET['pszPurchorderNum']:false;
$result = (isset($_GET['result']))?$_GET['result']:false;

if (!$store || !$order || $result===false) // === porque con 0 nos puede devolver un falso false
  throw new Exception ("Datos insuficientes."); // Error, no hemos recibido uno de los dos datos

if ($store!='PI00...')
  throw new Exception ("Código de tienda erróneo"); // Error, no me han pasado el código de tienda bien

// Aquí debemos implementar un método de acceso al pedido, esto es sólo un ejemplo
if ($order!=12345)
  throw new Exception ("Pedido no valido"); // Error, el código de pedido no es válido

if ($result==OPERACION_ACEPTADA)
{
// Aquí tendremos que almacenar en base de datos que todo ha ido bien
}
elseif ($result==OPERACION_DENEGADA)
{
// Aquí tenemos que almacenar en base de datos que ha sido denegada
} else {
// Almacenamos que ha ocurrido un error con la operación (para comprobar el dato manualmente)
/* No deberíamos hacerlo, pero como no hemos implementado nosotros el TPV no sabemos los secretos
   con los que nos puede sorprender en este aspecto. */

}

El usuario vuelve a la página

El TPV nos da la opción de volver a la página de la tienda, y cuando volvamos, la tienda nos enviará algunos parámetros sobre la operación, aunque éstos sí que los puede ver el usuario, así que no debemos hacer caso a ninguno de los datos que se nos mande por aquí. Como mucho a pszPurchorderNum, nuestro order de toda la vida.
Digo esto porque aquí también aparecerán parámetros para aceptar o denegar la transacción, pero es tan fácil como que el usuario cambie un valor, para que en nuestra tienda aparezca un artículo como pagado, por lo que tenemos que hacer caso del paso anterior, ya que habíamos guardado el resultado de la transacción.
Aquí nos limitamos a recibir el order y a decirle al usuario si la operación fue aceptada o denegada (repito, de los valores que obtuvimos antes). Si por casualidad, queremos filtrar un poco el código del pedido, podemos comprobar la fecha, si hace más de 1h que se hizo la transacción, el usuario no tendrá derecho a ver su estado (por ejemplo si un aprendiz de hacker se pone a introducir números de pedido al azar a ver lo que dice la página).

URLs involucradas

En nuestra tienda, tenemos que habilitar URLs para:

Configurando las URLs en nuestro sistema

Para ello debemos entrar en: https://tpv.4b.es/consultas, e introducir nuestros datos (la clave de comercio y el número de usuario suele ser el mismo) y entrar en la configuración, o en https://tpv.4b.es/config ahí nos encontraremos algo como esto:

Y rellenar los datos.

Paso a producción

Cuando todo esté listo, podemos dar el paso a producción, no es reversible, no podremos volver al entorno de pruebas (eso dicen, aunque no lo he probado). Lo malo, también es que el paso a producción tarda unas 6h, por lo que también hay que tenerlo en cuenta.

Actualización 13/02/2012 17:48: Gracias a Rodrigo por informar de un fallo en el código de ejemplo. Faltaba un CRLF y sobraba una tilde (manías de querer escribir bien) 🙂

También podría interesarte....