domingo, 28 de noviembre de 2010

Integrar el pago electrónico con paypal en java

Es increíble lo complicado que a veces puede resultar encontrar información sencilla sobre algo concreto. A la hora de la integración de una aplicación con sistemas de pago electrónico puedes encontrar algunos de los mejores ejemplos. Lo que voy a explicar es producto de un número demasiado elevado de horas buscando, leyendo, consultando y quebrándome la cabeza.

Y no solo con el caso específico de PayPal, sino con el resto de plataformas de pago con las que me he encontrado. Estoy pensando seriamente en crear un grupo en facebook "En contra de las explicaciones ambiguas para integrar plataformas de pago".

Bueno pues lo primero es saber si es complicado lo que tienes que hacer, porque lo que voy a contar es un pago estandar, es decir, una vez que en tu app ya sabes la cantidad que hay que pagar, quieres que se haga. Si quieres hacer pagos o gestionar tus cobros, o cosas más complejas con la interfaz SOAP, no es este tu artículo, jeje... Lo que vas a encontrar es lo que llaman:

  • Pago Express
  • Interfaz para NVP (abreviado de name-value pair)
  • Pago Estándar

Siempre hay un entorno de pruebas, como ya esperabas, y que funcionará exactamente igual que en el entorno real. Esta es una de las diferencias (entre otras) con plataformas con las que he trabajado y PayPal.

En esta última, el proceso que en principio creemos sencillo, que es el de obtener un número de tarjeta ficticio que sea aceptado por la validación de la tarjeta en la plataforma de pruebas, me implicó crear una cuenta ficticia de cliente en Paypal Sandbox y asociarle tarjetas.

Después de mucho clickar y urgar por el sandbox, trás intentar comprender que tipo de pago quiero realizar (..ufff) o ver mil veces el esquema de pago, tienes que tener una cuenta de empresa (o también llamado comerciante) e ir al menú "Perfil" para encontrar lo que buscas.
  • Preferencias de notificación de pago instantánea (IPN)
  • Configuración de pago codificado

1. Preferencias de notificación de pago instantánea IPN

En la primera opción tienes que indicar la url a la que se notifica el pago mediante petición POST. Esto quiere decir que cuando el pago se confirma, desde paypal se hará una solicitud a esa url que pongas, e incluirá además de una copia de los valores en la petición que mandamos desde nuestra web (desde el navegador del usuario) algunos parámetros más como el identificador de operación, que es el que a ti te interesa. ¿Problemas con el IPN? Se pueden deber a varios factores:

  • El pago no se confirma: Esto ocurre con cantidades elevadas, paypal desde hace poco está obligado a retener las cantidades elevadas (no recuerdo ahora el intervalo exacto) y las contrasta. Tu tendrás que acceder a tu cuenta de paypal (del sandbox) y confirmar el pago manualmente. Probablemente ya te habrá caducado la sesión, y tendrás que tirarte un rato logeandote de nuevo, pero de tanto hacerlo, tienes la contraseña grabada a fuego en tu cerebro... ¿verdad? :P
  • La url de respuesta no es correcta: Claro suele pasar... cámbiala

2. Configuración de pago codificado

En la segunda opción, configuras el certificado que vas a usar para "firmar un botón". Esta impresionante acción, como todas, al final no es para tanto y no es más que tomar una cadena con la información del pago, firmarla, y añadir el texto de la firma (...en PEM) a un formulario que se dirija a Paypal y que los navegantes presionan en tu página cuando quieren realizar el pago. Y con esto pues ya está, ahora:
  • Te descargas El SDK para java, descomprimes e incluyes las librerías en tu aplicación en /WEB-INF/lib
  • Descárgate todos los jars de las librerías criptográficas bouncycastle latest releases e incluyes las librerías en tu aplicación en /WEB-INF/lib
  • Creas tus certificados para firmar e identificarte con paypal: Para esto necesitas instalar OpenSSL (en ubuntu con "sudo apt-get install openssl"... en windows... no lo sé...) y ejecutar:

    #Generas la clave privada, el CSR
    openssl genrsa -out prvkey.pem 1024
    #La auto firmas y obtienes un certificado público
    openssl req -new -key prvkey.pem -x509 -days 365 -out pubcert.pem
    #Generas un almacen de certificados pkcs12 con ellos
    openssl pkcs12 -export -inkey prvkey.pem -in pubcert.pem -out prvkey.p12


    Después de todo esto te aparecen un montón de ficheros de los cuales solo te hace falta el almacén de certificados prvkey.p12 y el certificado de paypal que debes descargarlo desde paypal en la opción de "Configuración de pago codificado".

Pues nada, ten a buen recaudo la contraseña para el acceso al almacen de certificados y el archivo p12 por que ahora viene cuando se usa.

Realización del pago, datos del pago y firma

Bueno pues ya estás en tu aplicación y estás desarrollando la parte en la que se realiza el pago, entonces, tu objetivo va a ser crear una página donde se indique al cliente lo que va a pagar y se muestre un botón que ponga "Pagar con Paypal". El código HTML del formulario lo pones tú, pero asegúrate de que tienes dos campos:

<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="encrypted" value="<%= signedPaymentStr %>" />


que se envían a la url que te pasan desde paypal (algo de documentación tendrás que leer).

Los datos del pago van en la cadena firmada del campo encrypted, y básicamente tendrás que rellenarlos creando una cadena con algunos parámetros. Aquí os dejo el código de un metodito que devuelve directamente la cadena que buscamos. Cópialo, pégalo y míra el Javadoc, te resultará fácil. Ten en cuenta que el pago está configurado para EUROS!!...



import com.paypal.wpstoolkit.util.PPCrypto;
import java.util.Properties;
import org.apache.log4j.Logger;

.....
.....



/**
*
* Obtiene la cadena del botón encriptada para una compra. Una cadena de
* ejemplo sería:
*
* cert_id=...
* cmd=_xclick
* business=....
* item_name=Lorem Ipsum Dolor sit amet...
* item_number=1234
* custom=sc-id-789
* amount=500.00
* currency_code=EUR
* tax=41.25
* shipping=20.00
* address_override=1
* address1=Calle inventada
* city=Ciudad
* state=Estado
* zip=23452345
* country=ES
* no_note=1
* cancel_return=http://url.de.retorno
* @param paymentData Variables adicionales que se quieran adjuntar.
* @param concepto Descripción de concepto por el que se va a pagar.
* @param codigoVenta Identificador de la venta
* @param cantidad Cantidad en euros que se va a cobrar
* @param impuesto Cantidad que se va a cobrar de impuestos
* @param direccion Dirección del pagador
* @param numero numero de la direccion postal
* @param cp Codigo postal
* @param localidad Localidad de la dirección postal
* @param pais País de la dirección postal
* @param nombre Nombre del pagador
* @param apellidos Apellidos del pagador
* @param pruebas Booleano que indica si se dirigirá al entorno de pruebas o no
* @return cadena de la firma de los datos.
* @throws Exception
*/
public String getButtonEncryptionValue(String paymentData,
String concepto, String codigoVenta, Float cantidad, Float impuesto,
String direccion, String numero, String piso, String puerta,
String cp, String localidad, String pais, String nombre,
String apellidos, String email, Boolean pruebas) throws Exception {

Properties oProps = new Properties();
ClassLoader oCL = Thread.currentThread().getContextClassLoader();

InputStream oIStr = oCL.
getResourceAsStream("archivodeconfiguracionenclasspathquesiempreexiste.properties");
oProperties.load(oIStr);
oIStr.close();

String env = ".sandbox";
if (!pruebas) {
env = "";
}

String signedStr = null;

String _keyPass = "password_que_tienes_que_poner_bien";
String user = "userquetienesqueponerbien";
String certId = oProps.getProperty("propiedad_con_el_valor_que_te_da_paypal_al_registrarte");
String returnURL = oProps.getProperty("propiedad_url_de_vuelta_en_navegador_despues_de_pagar");
String returnCURL = oProps.getProperty("propieda_de_la_url_si_cancela_el_pago");
if (piso == null) {
piso = "";
}
if (puerta == null) {
puerta = "";
}
if (_data == null) {
_data = "";
}
if (numero != null && numero.length() > 0) {
direccion += " nº " + numero;
}
if (piso != null && piso.length() > 0) {
direccion += " - " + piso + "º";
}
if (puerta != null && puerta.length() > 0) {
direccion += " " + puerta;
}

paymentData += "cert_id=" + certId + ","
+"item_name=" + concepto.replaceAll(",", "") + ","
+"item_number=" + codigoVenta + ","
+"amount=" + String.format("%1.2f", cantidad).replaceAll(",", ".") + ","
+"tax=" + String.format("%1.2f", impuesto).replaceAll(",", ".") + ","
+"address_override=1,"
+"address1=" + direccion.replaceAll(",", "") + ","
+"city=" + localidad + ","
+"zip=" + cp + ","
+"country=" + pais + ","
+"first_name=" + nombre + ","
+"last_name=" + apellidos + ","
+"payer_email=" + email + ","
+"cmd=_xclick,"
+"business=" + user + ","
+"currency_code=EUR,"
+"lc=" + pais + ","
+"cbt=Volver a la página,"
+"return=" + returnURL + ","
+"cancel_return=" + returnCURL + "";

logger.info("Encoding data: " + paymentData);

paymentData = paymentData.replace(',', '\n');
try {
signedStr = new String(PPCrypto.getButtonEncryptionValue(paymentData.getBytes("UTF-8"),
oProps.getProperty("ruta_al_archivo_p12"),
oProps.getProperty("ruta_al_certificado_de_paypal"),
_keyPass));
} catch (Exception ex) {
logger.error("Excepcion firmando: " + ex.getMessage());
}
return signedStr;
}

4 comentarios:

  1. Lo provare,buen aporte.

    ResponderEliminar
  2. Buen aporte, porque como dices, parece mentira lo dificil que puede ser encontrar algo concreto con esto de las pasarelas de pago...
    XD

    Thanks!!

    ResponderEliminar
  3. Muchas gracias, voy a probarlo, llevaba mucho tiempo buscando esto.

    ResponderEliminar
  4. Hola, esto sigue vigente? Existe la librería para maven?

    Gracias

    ResponderEliminar

¿Que te parece? Deja un comentario