se basa en la comunicación con el core Bancario utilizando el estandar ISO 8583
En la siguiente dirección se puede ver un poco de teoría de la definición de ISO 8583: http://es.wikipedia.org/wiki/ISO_8583
Para implementar el proyecto nos basamos en la librería http://sourceforge.net/projects/trx-framework/ la cual es muy buena para el tema de armar los mensajes ISO8583
Crear los mensajes y procesar las respuestas es muy facil, lo interesante en este proyecto fue el manejo de los puertos de comunicación con el core.
Basicamente para mandar un mensaje hay que:
Iso8583Message MensajeEnviar = new Iso8583Message(2000);
MensajeEnviar.Fields.Add(32, 310000);
MensajeEnviar.Fields.Add(2, sNumTarjetaDebito);
Comun Sender = new Comun();
RespuestaDTO = Sender.EnviarMensaje(MensajeEnviar);
//Si la respuesta es exitosa vamos a revisar el mensaje
if (RespuestaDTO.bExitoso)
{
//Se verifica el campo 11 que es el utilizado como identificador unico del mensaje
if (RespuestaDTO.MensajeRecibido.Fields[TipoCampo.Field11Trace] != null)
//Revisamos el campo 39 que contiene el código de respuesta del mensaje
switch (int.Parse(RespuestaDTO.MensajeRecibido.Fields[TipoCampo.Field39Respuesta].ToString()))
{
//En caso que sea exitoso (00) procesamos el mensaje
//La respuesta del mensaje generalmente viene como un string en uno de los campos, en este caso
//La respuesta es todos la consulta consolidada del cliente dado la cual viene en el campo 48
//En este ejemplo está definido que cada registro de la respuesta es de 126 caracteres con lo cual
//procedemos a construir un array con cada uno de los registros del campo 48 y de ahí sacamos la data
case CodigoRespuesta.RespuestaExitosa:
ProductoDTO.bExitoso = true;
ModeloObjetos.Banca.Cliente Cliente = new ModeloObjetos.Banca.Cliente();
string sProducto = RespuestaDTO.MensajeRecibido.Fields[TipoCampo.Field48Productos].ToString();
//Se crea el array de de registros (a través de un metodo utilitario)
Array sRegistros = Comun.ProcesarResultado(RespuestaDTO.MensajeRecibido.Fields[TipoCampo.Field48Productos].ToString(), 126, 0);
//se recorren cada uno de los registros
foreach (string sRegistro in sRegistros)
{
ModeloObjetos.Banca.Producto Producto = new ModeloObjetos.Banca.Producto();
//Extraemos la data de cada campo de acuerdo a lo especificado por el core
Producto.sNumeroProducto = sRegistro.Substring(0, 20).Trim();
Producto.sCodProducto = sRegistro.Substring(20, 6).Trim();
Producto.sProductoNombre = sRegistro.Substring(26, 40).Trim();
Producto.enumTipoProducto = Comun.ObtenerTipoProducto(Producto.sCodProducto);
Producto.dSaldo1 = double.Parse(Comun.FormatearMontoIso(sRegistro.Substring(66, 12)));
Producto.dSaldo2 = double.Parse(Comun.FormatearMontoIso(sRegistro.Substring(78, 12)));
Producto.dSaldo3 = double.Parse(Comun.FormatearMontoIso(sRegistro.Substring(90, 12)));
if (Producto.enumTipoProducto == enumTipoProducto.DPF)
{
Producto.dtFecha = Comun.FormatearFechaStringCorta(sRegistro.Substring(106, 8));
Producto.dSaldo4 = double.Parse(Comun.FormatearMontoIso(sRegistro.Substring(114, 12)));
}
else if (Producto.enumTipoProducto == enumTipoProducto.TDC)
{
Producto.dSaldo4 = double.Parse(Comun.FormatearMontoIso(sRegistro.Substring(102, 12)));
Producto.dtFecha = Comun.FormatearFechaStringCorta(sRegistro.Substring(118, 8));
}
else
{
Producto.dSaldo4 = double.Parse(Comun.FormatearMontoIso(sRegistro.Substring(102, 12)));
}
ProductoDTO.Items.Add(Producto);
}
ProductoDTO.Items.Sort(ProductoDTO);
break;
default:
ProductoDTO.bExitoso = false;
ProductoDTO.sMensaje = RespuestaDTO.sMensaje;
break;
}
else
{
ProductoDTO.bExitoso = false;
ProductoDTO.sMensaje = "Error campo de respuesta (39) nulo";
}
}
else
{
// El mensaje ha expirado
ProductoDTO.bExitoso = false;
ProductoDTO.sMensaje = "MensajeExpiro";
}
Como comenté antes, lo interesante es el manejo de los puertos, que aunque en realidad es sencillo, tuvimos que darle muchas vueltas para asegurar la confiabilidad y estabilidad del servicio
La solución implementada se basa en utilizar un pool de puertos para comunicarnos con el core, este pool fue implementado como una cola:
static Queue
De está cola, antes de enviar un mensaje el código agarra un puerto, verifica que esté bueno, se envía el mensaje y se devuelve el puerto a la cola.
Si el mensaje falla o expira, se considera que el puerto está malo y por ende se agrega el mismo a otra cola de puertos en cuarentena.
Cada cierto tiempo se intenta mandar un mensaje ISO 8583 de prueba para los puertos en cuarenta, si el mensaje es exitoso, se saca el puerto de cuarentena y se devuelve el mismo a la cola de puertos disponibles para los mensajes
///
/// Metodo encargado del envío de mensasjes ISO8583
///
///
Mensaje ISO 8583 a ser enviado
///
public ModeloObjetos.DTO.MensajeDTO EnviarMensaje(Iso8583Message Mensaje)
{
int campoConsecutivo = 11;
//se agrega el campo 11 que sirve como identificador unico del mensaje (es obligatorio)
Mensaje.Fields.Add(campoConsecutivo, NuevoNumeroSecuencia.ToString());
ModeloObjetos.DTO.MensajeDTO RespuestaDTO = new ModeloObjetos.DTO.MensajeDTO();
Trx.Messaging.Iso8583.Iso8583Message Respuesta = null;
int iNumIntentos = 0;
ClientPeer Canal = null;
int canalesDisponibles;
//se asegura la exclusividad mientras se revisa si hay puertos disponibles para el envío del mensaje
lock (SyncRoot)
{
while (CanalesComunicacion.Count == 0 && iNumIntentos < _NumeroIntentosEsperaCanalISO)
{
System.Threading.Thread.Sleep(_TiempoEsperaCanalDisponibleISO);
iNumIntentos++;
}
if ((canalesDisponibles = CanalesComunicacion.Count) > 0)
{
iNumIntentos = 0;
while (iNumIntentos < _NumeroIntentosEsperaCanalISO)
{
for (int i = 0; i < canalesDisponibles; i++)
{
//al tener el canal disponible lo saco de la cola para usarlo
Canal = CanalesComunicacion.Dequeue();
if (Canal.IsConnected)
{
break;
}
else
{
CanalesComunicacion.Enqueue(Canal);
}
}
if (Canal.IsConnected)
{
break;
}
System.Threading.Thread.Sleep((int)System.Math.Pow(2, iNumIntentos) * 1000);
iNumIntentos++;
}
}
}
bool canalQuebrado = false;
//cuando se determina que hay un canal disponible nos disponemos a usarlo
if (canalesDisponibles > 0)
{
try
{
if (Canal.IsConnected)
{
RespuestaDTO.Trazas.Add(new ModeloObjetos.DTO.TrazaOperacionDTO(false, "MensajeAenviar:" + Mensaje.ToString()));
//Asigno el canal al request a enviar
PeerRequest request = new PeerRequest(Canal, Mensaje);
//Se envía el mensaje
request.Send();
//esperamos por la respuesta del mensaje ( de acuerdo al timeout establecido)
request.WaitResponse(_TimeoutRecepcionMensajeISO);
//Si el request no expiró, entonces está todo bien y guardamos la respuesta ISO en nuestro DTO de respuesta
if (!request.Expired)
{
if (ServiciosISOServer.Comun.Logger.IsDebugEnabled)
ServiciosISOServer.Comun.Logger.Debug("Respuesta: " + request.ResponseMessage.ReceivedData);
Respuesta = (Iso8583Message)request.ResponseMessage;
RespuestaDTO.bExitoso = true;
RespuestaDTO.Trazas.Add(new ModeloObjetos.DTO.TrazaOperacionDTO(false, "RespuestaMensaje:" + Respuesta.ToString()));
}
else
{
//Mandamos un correo avisando que el puerto está malo (para que la gente del core lo revise)
RespuestaDTO.Trazas.Add(new ModeloObjetos.DTO.TrazaOperacionDTO(false, "MensajeExpiro"));
string puerto = ((TcpChannel)Canal.Channel).Port.ToString();
string correo = ConfigurationManager.AppSettings["CorreoOperador"];
string sText = String.Format("Expiró un mensaje enviado al Servidor {0} Puerto: {1}", ((TcpChannel)Canal.Channel).HostName, puerto);
string sSubject = String.Format(ConfigurationManager.AppSettings["Subject"], puerto);
string sResultado = EnviarCorreo.EnvioCorreo(correo, sSubject, sText);
if (ServiciosISOServer.Comun.Logger.IsDebugEnabled)
ServiciosISOServer.Comun.Logger.Debug(sText + " enviado a " + correo + " Resultado: " + sResultado);
canalQuebrado = true;
}
//if(request.ResponseMessage != null)
// RespuestaDTO.Trazas.Add(new ModeloObjetos.DTO.TrazaOperacionDTO( false, "DataRecibida:" + request.ResponseMessage.ReceivedData));
}
else
{
RespuestaDTO.Trazas.Add(new ModeloObjetos.DTO.TrazaOperacionDTO(false, "CanalNoConectado"));
}
}
finally
{
lock (SyncRoot)
{
//Si el canal fue utilizado satisfactoriamente, lo agregamos de nuevo a la cola de puertos disponibles para envío de mensajes
if (canalQuebrado == false)
{
CanalesComunicacion.Enqueue(Canal);
}
else
{
if (ServiciosISOServer.Comun.Logger.IsDebugEnabled)
{
ServiciosISOServer.Comun.Logger.DebugFormat("El canal {0} ha sido movido a la cola de cuarentena", Canal.Name);
}
//Si el mensaje expiró, agregamos el puerto a la cola de cuarentena (para que no sea nuevamente usado para enviar mensajes de la app hasta que esté sano)
MonitorCuarentena.Current.AgregarObjetoCuarentena(Canal);
}
}
}
}
else
{
RespuestaDTO.Trazas.Add(new ModeloObjetos.DTO.TrazaOperacionDTO(false, "CanalesNoDisponibles"));
}
RespuestaDTO.MensajeEnviado = Mensaje;
RespuestaDTO.MensajeRecibido = (Respuesta != null ? Respuesta : new Iso8583Message());
if (ServiciosISOServer.Comun.Logger.IsDebugEnabled)
ServiciosISOServer.Comun.Logger.Debug("Retornando: " + RespuestaDTO.bExitoso);
return RespuestaDTO;
}
Otra cosa a tener en cuenta es que seguro tendrá que hacer cambios en la especificación del mensaje ISO8583 en la clase: Iso8583Ascii1987MessageFormatter, en esta clase
están definidos como debe ser cada uno de los campos de los mensajes ISO por ejemplo:
FieldFormatters.Add( new StringFieldFormatter(
48, new VariableLengthManager(0, 9999, StringLengthEncoder.GetInstance(9999)),
StringEncoder.GetInstance(), "Additional data - private" ) );
Aquí se indica que el campo 48 es tipo String de longitud variable y en este caso con una longitud maxima de 9999 caracteres
En este otro ejemplo el campo 42 es string pero con longitud fija de 15
FieldFormatters.Add( new StringFieldFormatter(
42, new FixedLengthManager( 15 ), StringEncoder.GetInstance(),
"Card acceptor identification code" ) );
Estas definiciones son muy importantes ya que son las primeras que arrojan errores en las pruebas ya que cada server implementa a veces el estandar a su manera
Si está interesado en consultoría en comunicación ISO 8583 o necesita implementar una solución de este tipo, por favor no dude en contactarme.
Luego publicaré una aplicación de pruebas llamada ISO Tester que sirve para hacer pruebas puntuales de este tipo de mensajería