Commit 2e963f0b authored by bsarmiento31's avatar bsarmiento31
Browse files

Initial commit

parents
using EvaPosSrvCmd;
using EvaPosSrvResp;
using EvaPosSrvAplicacion;
using System.Threading.Tasks;
namespace EvaPosSrvAplicacionImp
{
/// <summary>
/// Clase que ejecuta el comando y retorna respuesta.
/// </summary>
public class Aplicacion : IAplicacion
{
public Respuestas Procesar(IComando comando)
{
return comando.Ejecutar();
}
}
}
\ No newline at end of file
using Serilog;
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
// Comandos que procesan las DTOs.
namespace EvaPosSrvCmd
{
/// <summary>
/// Interface de definición de comandos.
/// </summary>
public interface IComando
{
/// <summary>
/// Identifica el mensaje al cual el comando responde.
/// La referencia es usada por el servidor de mensajes para asociar
/// un tipo de mensaje con el comando respectivo.
/// La referencia corresponde a un string contenido en el mensaje
/// que permite identificar su tipo.
/// </summary>
public string Referencia { get; set; }
/// <summary>
/// Carga el DTO con los datos a procesar.
/// Este método debe ser invocado antes de ejecutar el comando,
/// para inicializarlo con los datos necesarios para ejecutar.
/// </summary>
public void CargaDTO(DTOBase dto);
/// <summary>
/// Ejecuta el comando, retorna objeto DTO de respuesta.
/// </summary>
public Respuestas Ejecutar();
/// <summary>
/// Retorna una "shallow copy" del objeto.
/// </summary>
public IComando CreaCopia();
}
/// <summary>
/// Directorio de comandos.
/// Almacena instancia de comandos indexadas por su Referencia.
/// Usado para identificar el comando que procesa un mensaje particular
/// asociado por la propiedad Referencia del comando.
/// </summary>
public class DirectorioCmds
{
private Dictionary<string, IComando> dir = new Dictionary<string, IComando>();
/// <summary>
/// Agrega un comando al directorio.
/// </summary>
public void AgregaComando(IComando cmd)
{
dir.Add(cmd.Referencia, cmd);
}
/// <summary>
/// Recupera una "shallow copy" del comando asociado al parámmetro.
/// </summary>
public IComando ObtieneComando(string referencia)
{
IComando cmd;
try {
cmd = dir[referencia];
} catch (KeyNotFoundException)
{
throw new Exception($"No se encuentra comando con referencia '{referencia}'.");
}
return cmd.CreaCopia();
}
}
}
\ No newline at end of file
using EvaPOS_API_FRAME.Comandos;
using EvaPOS_API_FRAME.RespuestasXML;
using EvaPosSrvCmd;
using EvaPosSrvDTO;
using EvaPosSrvResp;
using EvaPosSrvRespImp;
using Serilog;
/******************************************************************
ESPACIO DE NOMBRES PARA IMPLEMENTAR COMANDOS.
*******************************************************************/
namespace EvaPosCmdImp
{
/// <summary>
/// Clase para inicializar el directorio de comandos.
/// </summary>
public class IniciaDirectorioCmds
{
public DirectorioCmds DirectorioCmds {get; private set;} = new DirectorioCmds();
public IniciaDirectorioCmds AgregaCmd(IComando cmd)
{
DirectorioCmds.AgregaComando(cmd);
return this;
}
// TODO - Evaluar eliminar este paso por una forma automática en el mismo comando.
/// <summary>
/// Este método instancia cada comando y lo agrega al directorio de comandos.
/// Agregar cada comando aquí.
/// </summary>
public static IniciaDirectorioCmds Cargar()
{
return new IniciaDirectorioCmds()
.AgregaCmd(new ErrorCmd())
.AgregaCmd(new AddItemRequestCmd())
.AgregaCmd(new InitializeRequestCmd())
.AgregaCmd(new QueryStatusRequestCmd())
.AgregaCmd(new ReportStatusEventsRequestCmd())
.AgregaCmd(new SignOffResponseCmd())
.AgregaCmd(new AddReceiptLinesRequestCmd())
.AgregaCmd(new GetTotalsRequestCmd())
.AgregaCmd(new AddTenderDebitCmd())
.AgregaCmd(new PrintCurrentReceiptsRequestCmd())
.AgregaCmd(new ReprintReceiptsCmd())
.AgregaCmd(new TerminateRequestCmd())
.AgregaCmd(new VoidTransactionCmd())
.AgregaCmd(new RemoveReceiptLinesCmd())
.AgregaCmd(new CancelActionCmd())
.AgregaCmd(new AddCustomerBirthdateCmd())
.AgregaCmd(new AddCustomerCmd())
.AgregaCmd(new SuspendTransactionRequestCmd());
}
}
}
\ No newline at end of file
// Espacio de nombres que reune objetos DTO (data transfer object ) para comunicación con capa de presentación.
// Los DTO no incluyen lógica, solo datos (no son un remplazo de las Entidades - objetos del negocio.)
using System;
namespace EvaPosSrvDTO
{
/// <summary>
/// TipoMensaje encapsula identificadores permitidos de tipo de mensaje.
/// </summary>
public class TipoMensaje
{
/// <summary>
/// Valor del tipo de mensaje.
/// </summary>
//REVISAR: Creado para xml, set era private
public string Tipo { get; set; }
//REVISAR: Creado para xml constructor vacio
public TipoMensaje()
{
}
private TipoMensaje(string tipo)
{
Tipo = tipo;
}
/// <summary>
/// Mensajes tipo EVENT.
/// </summary>
//REVISAR: Creado para xml, set era private
public static TipoMensaje Event { get; set; } = new TipoMensaje("EVENT");
/// <summary>
/// Mensajes tipo REQest.
/// </summary>
//REVISAR: Creado para xml, set era private
public static TipoMensaje Req { get; set; } = new TipoMensaje("REQ");
/// <summary>
/// Mensajes tipo RESPonse.
/// </summary>
//REVISAR: Creado para xml, set era private
public static TipoMensaje Resp { get; set; } = new TipoMensaje("RESP");
/// <summary>
/// Retorna un tipo de mensaje correspondiente al parámetro.
/// </summary>
public static TipoMensaje FijaTipoMensaje(string tipo)
{
var t = tipo switch
{
"REQ" => TipoMensaje.Req,
"EVENT" => TipoMensaje.Event,
"RESP" => TipoMensaje.Resp,
_ => throw new Exception($"Tipo de mensaje no reconocido {tipo}")
};
return t;
}
}
/// <summary>
/// Clase base de DTO.
/// </summary>
public abstract class DTOBase
{
/// <summary>
/// Id de la sesión.
/// </summary>
//REVISAR: Creado para xml, set era private
public Int32 IdSesion { get; set; }
/// <summary>
/// Tipo del mensaje.
/// </summary>
//REVISAR: Creado para xml, set era private
public TipoMensaje TipoMensaje { get; set; }
/// <summary>
/// Constructor base.
/// </summary>
public DTOBase(int idSesion, TipoMensaje tipo)
{
IdSesion = idSesion;
TipoMensaje = tipo;
}
//REVISAR: Creado para xml, set era private, cosntructor vacio
public DTOBase()
{
}
}
// TODO - ARREGLAR EL MECANISMO DE TERMINACION DEL SERVIDOR.
/// <summary>
/// DTO con mensaje de terminación de conexión, para pruebas.
/// </summary>
public class FinDePruebaDTO : DTOBase
{
/// <summary>
/// Mensaje de terminación de conexión, para pruebas.
/// </summary>
public string MensajeFin { get; private set; } = "<|ACK|>";
public FinDePruebaDTO() : base(1, TipoMensaje.Event)
{}
}
// TODO - validar manejo de errores y su notificación a clientes que interactuan con el servidor.
/// <summary>
/// Retorna mensajes de error.
/// </summary>
public class ErrorDTO : DTOBase
{
/// <summary>
/// Texto del mensaje de error.
/// </summary>
public string Texto { get; private set; }
/// <summary>
/// Constructor con texto del mensaje de error.
/// </summary>
public ErrorDTO(int sessionId, TipoMensaje messageType, string texto) : base(sessionId, messageType)
{
Texto = texto;
}
/// <summary>
/// Representación del objeto en un string.
/// </summary>
public override string ToString()
{
return $"ErrorDTO - Texto: {Texto}";
}
}
/// <summary>
/// InitializeRequest DTO
/// </summary>
public class InitializeRequestDTO : DTOBase
{
/// <summary>
/// Campo OperatorID
/// </summary>
public string OperatorID { get; set; }
/// <summary>
/// Campo TerminalNumber
/// </summary>
public int TerminalNumber { get; set; }
/// <summary>
/// Campo Recovery
/// </summary>
public bool Recovery { get; set; }
public InitializeRequestDTO(int sessionId, TipoMensaje messageType) : base(sessionId, messageType) {}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Xml;
using EvaPosSrvDTO;
namespace EvaPosSrvResp
{
/// <summary>
/// Interface que define adaptadores para conversión de
/// mensaje XML en DTO.
/// </summary>
public interface IAdaptadorDTO
{
/// <summary>
/// Identifica el mensaje al cual el adaptador genera DTO.
/// Le referencia se usa por el servidor de mensajes para interpretar
/// el mensaje en un DTO a pasar a un comando.
/// La referencia corresponde a un string contenido en el mensaje
/// que permite identificar su tipo.
/// </summary>
public string Referencia { get; set; }
/// <summary>
/// Nombre del elemento xml con los datos para el DTO.
/// El nombre del elemento se presenta en formato xpath para ubicar el nodo desde el
/// elemento raiz del mensaje.
/// </summary>
public string NombreElementoXMLDatos { get; set; }
/// <summary>
/// Inicializar DTO adecuado al mensaje.
/// </summary>
public DTOBase ObtieneDTO(int idSesion, TipoMensaje tipoMensaje, XmlElement docXml);
/// <summary>
/// Retorna una "shallow copy" del objeto.
/// </summary>
public IAdaptadorDTO CreaCopia();
}
/// <summary>
/// Directorio de adaptadores DTO.
/// Almacena instancia de adaptadores indexadas por su Referencia.
/// Usado para identificar el adaptador que procesa un mensaje particular
/// asociado por la propiedad Referencia del adaptador.
/// </summary>
public class DirectorioAdaptadoresDTO
{
private Dictionary<string, IAdaptadorDTO> dir = new Dictionary<string, IAdaptadorDTO>();
/// <summary>
/// Agrega un adaptador al directorio.
/// </summary>
public void AgregaAdaptador(IAdaptadorDTO adp)
{
dir.Add(adp.Referencia, adp);
}
/// <summary>
/// Recupera una "shallow copy" del adaptador asociado al parámmetro.
/// </summary>
public IAdaptadorDTO ObtieneAdaptador(string referencia)
{
IAdaptadorDTO adp;
try {
adp = dir[referencia];
} catch (KeyNotFoundException)
{
throw new Exception($"No se encuentra adaptador con referencia '{referencia}'.");
}
return adp.CreaCopia();
}
}
/// <summary>
/// Trama, integra las partes que incluyen mensaje xml e información de control.
/// </summary>
public struct TramaSCO
{
/// <summary>
/// Longitud del mensaje.
/// Es la suma de la longitud de las cadenas de texto de la sección de encabezado
/// y de la sección te contenido xml.
/// </summary>
public UInt32 Longitud { get; set; }
/// <summary>
/// Id de la sesión.
/// </summary>
public Int32 IdSesion { get; set; }
/// <summary>
/// Tipo del mensaje, con valores predefinidos según el tipo de datos.
/// </summary>
public TipoMensaje TipoMensaje { get; set; }
/// <summary>
/// Texto del encabezado según los valores de tipo de mensaje e id de sesión.
/// </summary>
public string TextoEncabezado { get => $"soeps~Message-Type={TipoMensaje.Tipo}|Session-Id={IdSesion}|~"; }
/// <summary>
/// Texto del contenido xml del mensaje, con sus valores incluidos.
/// Debe ser inicializado por el que crea el objeto.
/// </summary>
public string TextoXML { get; set; }
/// <summary>
/// Objeto xml del mensaje, construido a partir del texto xml.
/// </summary>
public XmlDocument ContenidoXML
{
get
{
var doc = new XmlDocument();
doc.LoadXml(TextoXML);
return doc;
}
}
/// <summary>
/// Constructor genérico.
/// </summary>
public TramaSCO(UInt32 longitud, Int32 idSesion, TipoMensaje tipo, string textoXML)
{
Longitud = longitud;
IdSesion = idSesion;
TipoMensaje = tipo;
TextoXML = textoXML;
}
/// <summary>
/// Constructor usado para calcular Longitud del mensaje (encabezado + texto xml).
/// </summary>
public TramaSCO(Int32 idSesion, TipoMensaje tipo, string textoXML)
{
IdSesion = idSesion;
TipoMensaje = tipo;
TextoXML = textoXML;
Longitud = Convert.ToUInt32(TextoEncabezado.Length + TextoXML.Length);
}
}
/// <summary>
/// Definición mensajes respuesta para el SCO.
/// </summary>
public abstract class Respuesta
{
/// <summary>
/// String con el encabezado y contenido xml de mensajes de respuesta.
/// </summary>
public Int32 SessionId { get; private set; }
public TipoMensaje MessageType { get; private set; }
public abstract string TextoXML { get; }
public TramaSCO TramaSCO
{
get => new TramaSCO(SessionId, MessageType, TextoXML);
}
public Respuesta(int sessionId, TipoMensaje tipo)
{
SessionId = sessionId;
MessageType = tipo;
}
}
/// <summary>
/// Respuesta en modo texto genérico. Usada para pruebas básicas de comunicación.
/// </summary>
public class RespuestaGenerica : Respuesta
{
private string _mensaje;
/// <summary>
/// Constructor mensaje básico.
/// </summary>
public RespuestaGenerica(string mensaje) : base(1, TipoMensaje.Resp)
{
_mensaje = mensaje;
}
/// <summary>
/// Retorna mensaje.
/// </summary>
public override string TextoXML { get => _mensaje; }
}
/// <summary>
/// Colección de respuestas.
/// </summary>
public class Respuestas : List<Respuesta> { }
}
\ No newline at end of file
using System;
using System.Xml;
using EvaPOS_API_FRAME.Adaptadores;
using EvaPosSrvDTO;
using EvaPosSrvResp;
using EvaPosUtil;
// ****************************************************************
//
// ESPACIO DE NOMBRES PARA IMPLEMENTAR ADAPTADORES Y RESPUESTAS
//
// ****************************************************************
namespace EvaPosSrvRespImp
{
/// <summary>
/// Clase para inicializar el directorio de adaptadores de entrada.
/// Los adaptadores de entrada inicializan al dto asociada con el mensaje de entrada.
/// Para agregar un adaptador de entrada, modifique el método ' public static IniciaDirectorioAdaptadores Cargar()'
/// agregando la nueva instancia del objeto adaptador, usando el método 'AgregaAdaptador'. Ejemplo:
///
/// public static IniciaDirectorioAdaptadores Cargar()
/// {
/// return new IniciaDirectorioAdaptadores()
/// .AgregaAdaptador(new AdaptadorInitializeRequest())
/// .AgregarAdaptador(new AdaptadorQueryStatusRequest());
/// }
///
/// Agrega nueva instancia del AdaptadorQueryStatusRequest() al directorio de adaptadores.
/// </summary>
public class IniciaDirectorioAdaptadores
{
public DirectorioAdaptadoresDTO DirectorioAdaptadores {get; private set;} = new DirectorioAdaptadoresDTO();
public IniciaDirectorioAdaptadores AgregaAdaptador(IAdaptadorDTO adaptador)
{
DirectorioAdaptadores.AgregaAdaptador(adaptador);
return this;
}
public static IniciaDirectorioAdaptadores Cargar()
{
return new IniciaDirectorioAdaptadores()
.AgregaAdaptador(new AdaptadorInitializeRequest())
.AgregaAdaptador(new AdaptadorQueryStatusRequest())
.AgregaAdaptador(new AdaptadorReportStatusEventsRequest())
.AgregaAdaptador(new AdaptadorAddItemRequest())
.AgregaAdaptador(new AdaptadorSignOffRequest())
.AgregaAdaptador(new AdaptadorAddReceiptLinesRequest())
.AgregaAdaptador(new AdaptadorGetTotalsRequest())
.AgregaAdaptador(new AdaptadorAddTenderDebitRequest())
.AgregaAdaptador(new AdaptadorPrintCurrentReceipts())
.AgregaAdaptador(new AdaptadorReprintReceiptsRequest())
.AgregaAdaptador(new AdaptadorTerminateRequest())
.AgregaAdaptador(new AdaptadoresVoidTransactionRequest())
.AgregaAdaptador(new AdaptadorRemoveReceiptLines())
.AgregaAdaptador(new AdaptadorCancelAction())
.AgregaAdaptador(new AdaptadorAddCustomerBirthdateRequest())
.AgregaAdaptador(new AdaptadorAddCustomerRequest())
.AgregaAdaptador(new AdaptadorSuspendTransactionRequest());
}
}
}
#define TRACE
#define DEBUG
// EvaPos-API : servidor api, sockets y rest.
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Xml;
using Serilog;
using Serilog.Events;
using EvaPosUtil;
using EvaPosSrvDTO;
using EvaPosSrvCmd;
using EvaPosSrvResp;
using EvaPosSrvAplicacion;
using EvaPosCmdImp;
using System;
using EvaPOS_API_FRAME.Comandos;
using System.Threading;
using EvaPOS_API_FRAME.RespuestasXML;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;
namespace EvaPosSCOSrv
{
/// <summary>
/// Esta clase <c>ServidorSocket</c>activa servidor socktes.
/// Usa por default ip localhost 127.0.0.1 y puerto 11.0000
/// La operación del servidor es como sigue:
///
/// </summary>
public class ServidorSocket
{
ILogger log = Log.ForContext<ServidorSocket>();
readonly static bool _isDebug = Log.IsEnabled(LogEventLevel.Debug);
/// <summary>
/// Longitud máxima de mensaje de entrada.
/// </summary>
public const Int32 LongMaxMensaje = 1_024 * 8;
/// <summary>
/// Dirección ip para vincular el socket.
/// </summary>
public string Direccion { get; private set; } = "127.0.0.1";
/// <summary>
/// Puerto a vincular socket.
/// </summary>
public Int32 Puerto { get; private set; } = 11_000;
/// <summary>
/// Longitud de la cola de conexión al socket.
/// </summary>
public int LongColaConexiones { get; private set; } = 10;
/// <summary>
/// Fija timeout usado para enviar / recibir em modo sincrónico, en milisegundos.
/// Si el timeout se excede, se presenta una excepción.
/// Default a 5 segundos.
/// </summary>
public int TimeOutSincMs { get; private set; } = 3000;
long _numeroConexionesEntrantes = 0;
DirectorioAdaptadoresDTO _adaptadores;
DirectorioCmds _comandos;
Sesion _sesion;
Presentacion _presentacion;
IAplicacion _aplicacion;
/// <summary>
/// Constructor servidor socket. Usa ip y puerto defualt.
/// El método IniciarAync() activa el servidor.
/// </summary>
public ServidorSocket() { }
/// <summary>
/// Constructor servidor socket. Usa ip y puerto defualt.
/// El método IniciarAync() activa el servidor.
/// <param name="direccion">Dirección IP servidor, típicamente '127.0.0.1'.</param>
/// <param name="puerto">Número de puerto para el socket, default 11.000</param>
/// <returns>Retorna tipo Task.</returns>
/// </summary>
public ServidorSocket(string direccion, Int32 puerto)
{
Direccion = direccion;
Puerto = puerto;
}
/// <summary>
/// Fija IP del servidor.
/// </summary>
public ServidorSocket ConIp(string direccion)
{
Direccion = direccion;
return this;
}
/// <summary>
/// Fija puerto del servidor.
/// </summary>
public ServidorSocket EnPuerto(int puerto)
{
Puerto = puerto;
return this;
}
/// <summary>
/// Fija directorio de adaptadores.
/// </summary>
public ServidorSocket AgregaDispensadorAdaptadores(DirectorioAdaptadoresDTO adaptadores)
{
_adaptadores = adaptadores;
return this;
}
/// <summary>
/// Fija directorio de comandos.
/// </summary>
public ServidorSocket AgregaDispensadorComandos(DirectorioCmds comandos)
{
_comandos = comandos;
return this;
}
/// <summary>
/// Fija aplicación a procesar comandos.
/// </summary>
public ServidorSocket AgregaProcesadorAplicacion(IAplicacion aplicacion)
{
_aplicacion = aplicacion;
return this;
}
// TODO - este metodo no se puede usar en la invocación fluida, estudiar.
/// <summary>
/// Pone en marcha el servidor socket, vinculando a la IP y puertos asignados.
/// </summary>
public ServidorSocket Activa()
{
_sesion = new Sesion();
_presentacion = new Presentacion(_adaptadores, _comandos);
ActivaServidor();
return this;
}
/// <summary>
/// Activa el servidor en ip y puerto indicado en los parámetros globales.
/// <returns>Retorna tipo Task.</returns>
/// </summary>
private void ActivaServidor()
{
IPEndPoint ipEndPoint = new(IPAddress.Parse(Direccion), Puerto);
int cont = 0;
// Clave usar 'using' para liberar correctamente recursos.
using Socket tcpSocket = new(
ipEndPoint.AddressFamily,
SocketType.Stream,
ProtocolType.Tcp);
// Configuración comportamiento socket tcp.
// No se permite a otro socket compartir el puerto.
// TODO - Investigar: cuando servidor y cliente están en la misma instanción de s.o.
// no comparten el puerto?.
//tcpSocket.ExclusiveAddressUse = true;
// El socket espera los segundos del parémetro para terminar de enviar
// datos (si hay en el buffer de salida), despues que se llama Socket.Close.
tcpSocket.LingerState = new LingerOption(true, 3);
// Desactiva algoritmo Nagle.
tcpSocket.NoDelay = true;
// Timeout entrada / salida.
tcpSocket.ReceiveTimeout = 0;
tcpSocket.SendTimeout = TimeOutSincMs;
//tcpSocket.Blocking = false;
tcpSocket.Bind(ipEndPoint);
tcpSocket.Listen(LongColaConexiones);
// Inicializa objetos de procesamiento de los mensajes de comunicación.
Sesion sesion = new Sesion();
Presentacion presentacion = new Presentacion(_adaptadores, _comandos);
// Id proceso. Compilación difiere según .net usado.
int id = -1;
#if NETFRAMEWORK
id = Process.GetCurrentProcess().Id;
#elif (NETSTANDARD || NET5_0_OR_GREATER)
id = Environment.ProcessId;
#endif
log.Information("EvaPOS servidor socket en {ip} : {puerto}, proceso id {id}", Direccion, Puerto, id);
if (_isDebug)
{
log.Debug("Versión .Net {version}", Environment.Version);
log.Debug("Longitud máxima mensaje aceptado bytes {long}", LongMaxMensaje);
Log.Debug("Tcp Socket configuración:");
Log.Debug($" Blocking {tcpSocket.Blocking}");
Log.Debug($" Version eva 1.3");
Log.Debug($" ExclusiveAddressUse {tcpSocket.ExclusiveAddressUse}");
Log.Debug($" LingerState {tcpSocket.LingerState.Enabled}, {tcpSocket.LingerState.LingerTime}");
Log.Debug($" NoDelay {tcpSocket.NoDelay}");
Log.Debug($" ReceiveBufferSize {tcpSocket.ReceiveBufferSize}");
Log.Debug($" ReceiveTimeout {tcpSocket.ReceiveTimeout}");
Log.Debug($" SendBufferSize {tcpSocket.SendBufferSize}");
Log.Debug($" SendTimeout {tcpSocket.SendTimeout}");
Log.Debug($" Ttl {tcpSocket.Ttl}");
Log.Debug($" IsBound {tcpSocket.IsBound}");
}
// TODO - El socket temporal debe liberar recursos?
// Socket entrada.
Socket socketEntrada;
try
{
bool continuar = true;
while (continuar)
{
log.Information("Esperando conexión...");
socketEntrada = tcpSocket.Accept();
continuar = ProcesaConexion(socketEntrada, _numeroConexionesEntrantes++);
while (!continuar)
{
tcpSocket.Close();
IPEndPoint ipEndPoint2 = new(IPAddress.Parse(Direccion), Puerto);
using Socket tcpSocket2 = new(
ipEndPoint2.AddressFamily,
SocketType.Stream,
ProtocolType.Tcp);
tcpSocket2.LingerState = new LingerOption(true, 3);
tcpSocket2.NoDelay = true;
tcpSocket2.ReceiveTimeout = 0;
tcpSocket2.SendTimeout = TimeOutSincMs;
tcpSocket2.Bind(ipEndPoint);
tcpSocket2.Listen(LongColaConexiones);
Sesion sesion2 = new Sesion();
Presentacion presentacion2 = new Presentacion(_adaptadores, _comandos);
socketEntrada = tcpSocket2.Accept();
continuar = ProcesaConexion(socketEntrada, _numeroConexionesEntrantes++);
}
}
if (tcpSocket.Connected)
tcpSocket.Shutdown(SocketShutdown.Both);
log.Information("Conexion Cerrada");
}
finally
{
tcpSocket.Close();
}
}
/// <summary>
/// Retorna longitud del mensaje representada en los 4 primeros bytes del arreglo bytes de argumento.
/// </summary>
private uint LongitudMensaje(byte[] arregloBytes)
{
// Extrae longitud. Primeros 4 bytes del arreglo.
byte[] bytesConLongMensaje = new byte[4];
Buffer.BlockCopy(arregloBytes, 0, bytesConLongMensaje, 0, 4);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytesConLongMensaje);
var longitud = BitConverter.ToUInt32(bytesConLongMensaje, 0);
return longitud;
}
/// <summary>
/// Procesa socket entrada de conexión.
/// </summary>
private bool ProcesaConexion(Socket socket, long nroConexion)
{
bool result = true;
Respuestas respuestas = null;
int contIngreso = 0;
if (_isDebug)
{
Log.Debug("Conexión remota #{nro} ip {ip} en puerto {pto}",
nroConexion,
IPAddress.Parse(((IPEndPoint)socket.RemoteEndPoint).Address.ToString()),
((IPEndPoint)socket.RemoteEndPoint).Port.ToString());
contIngreso++;
}
while (true)
{
try
{
// Lee mensajes hasta que llegue mensaje de terminación o socket cerrado por el cliente.
// Lee longitud mensaje entrante, 4 bytes.
Log.Debug("Esperando mensaje...");
var bufferLongitud = new byte[4];
int bytesLeidos = 0;
while (bytesLeidos < 4)
{
bytesLeidos += socket.Receive(bufferLongitud, bytesLeidos, bufferLongitud.Length, SocketFlags.None);
}
// Lee porción de datos del mensaje, hasta la longitud indicada en los 4 primeros bytes.
var longitudMensaje = LongitudMensaje(bufferLongitud);
Log.Debug("Longitud mensaje {long} - buffer longitud mensaje >>{buffer}<<", longitudMensaje, bufferLongitud);
if (longitudMensaje > LongMaxMensaje) throw new Exception($"Mensaje recibido de {longitudMensaje} bytes supera máximo permitido de {LongMaxMensaje} bytes.");
Log.Debug("Leyendo bytes con mensaje...");
var bufferEntrada = new byte[longitudMensaje];
bytesLeidos = 0;
while (bytesLeidos < longitudMensaje)
{
bytesLeidos += socket.Receive(bufferEntrada, bytesLeidos, bufferEntrada.Length, SocketFlags.None);
}
Log.Information("Bytes recibidos {bytes}", bytesLeidos);
// Procesando entrada: se obtiene mensaje, con el cual se
// identifica comando que lo procesa, se ejecuta el comando
// y se retornarn respuestas al cliente que ha emitido el mensaje.
TramaSCO msj = _sesion.Entrada(bufferEntrada, bytesLeidos);
IComando cmd = _presentacion.Entrada(msj);
//Enviamos la respuesta del pinpad primero del datafono antes de que entre al comando procesar
if (cmd.Referencia == "scsns:AddTender")
{
Respuestas respuestaDatafono = EjecutarProcesarDatafonoDirecto();
foreach (var respuesta in respuestaDatafono)
{
Log.Debug("Respuesta del pinpad del datafono");
var bufferSalida = _sesion.Salida(respuesta.TramaSCO);
socket.Send(bufferSalida, 0, bufferSalida.Length, SocketFlags.None);
Log.Information("Bytes enviados {bytes}", bufferSalida.Length);
}
}
respuestas = _aplicacion.Procesar(cmd);
Log.Debug("Respuestas de cmd ref '{cmd}' : {nroRespuestas}", cmd.Referencia, respuestas.Count);
// Enviando respuestas.
int i = 1;
foreach (var respuesta in respuestas)
{
Log.Debug("Respuesta #{i}", i++);
var bufferSalida = _sesion.Salida(respuesta.TramaSCO);
socket.Send(bufferSalida, 0, bufferSalida.Length, SocketFlags.None);
Log.Information("Bytes enviados {bytes}", bufferSalida.Length);
}
log.Information("Fin del ciclo, se enviaron {nro} respuestas", respuestas.Count);
if (cmd.Referencia == "scsns:Terminate")
{
result = false;
break;
}
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.ConnectionAborted)
{
// La conexión fue anulada por el software en el equipo remoto
log.Warning("La conexión fue anulada por el software en el equipo remoto.");
}
else
{
// Otra excepción de SocketException
log.Error("Error de Socket: {error}", ex);
}
result = false;
break;
}
catch (Exception e )
{
log.Error("Error : {error}" , e);
result = false;
break;
}
}
return result;
}
//Metodo de respuesta para el pinpad del datafono
public Respuestas EjecutarProcesarDatafonoDirecto()
{
Respuestas respuestaPinPad = null;
POSBCStatusEventPinPadStatus posbcEventWaitScreenPaidPad = null;
posbcEventWaitScreenPaidPad = new POSBCStatusEventPinPadStatus(1, TipoMensaje.Event, "INFO", "WAITING_FOR_PINPAD_INPUT", "Siga las instrucciones del Datafono");
respuestaPinPad = new Respuestas { posbcEventWaitScreenPaidPad };
return respuestaPinPad;
}
}
/// <summary>
/// Clase que controla el nivel básico de entrada y salida de mensajes.
/// Extrae la información del mensaje, limpiando la información de control.
/// </summary>
class Sesion
{
static ILogger log = Log.ForContext<Sesion>();
readonly static bool _isDebug = Log.IsEnabled(LogEventLevel.Debug);
/// <summary>
/// Interpreta arreglo de bytes en mensaje, extrayendo del arreglo de bytes
/// y copiando el contenido en un objeto tipo Trama.
/// </summary>
public TramaSCO Entrada(byte[] buffer, int nroBytes)
{
if (_isDebug)
{
log.Debug("Buffer entrada: >>{subBuffer}<<", buffer);
}
TramaSCO trama = new TramaSCO();
trama.Longitud = Convert.ToUInt32(nroBytes);
log.Debug("Longitud mensaje: {long}", trama.Longitud);
// Extrae encabezado. String con patron soeps~<texto>~
string datos = Encoding.UTF8.GetString(buffer, 0, nroBytes);
int inicioXML = datos.IndexOf("<");
string parteEncabezado = datos.Substring(0, inicioXML);
log.Debug("Encabezado: {encabezado}", parteEncabezado);
// Extrae valor campo Session-Id en string.
int inicioSessionId = parteEncabezado.IndexOf("Session-Id");
int finSessionId = parteEncabezado.Substring(inicioSessionId).IndexOf("|");
string sessionId = parteEncabezado.Substring(inicioSessionId, finSessionId);
string valorSessionId = sessionId.Split('=')[1];
trama.IdSesion = Int32.Parse(valorSessionId);
// Extrae valor campo Message-Type en string.
int inicioMessageType = parteEncabezado.IndexOf("Message-Type");
int finMessageType = parteEncabezado.Substring(inicioMessageType).IndexOf("|");
string messageType = parteEncabezado.Substring(inicioMessageType, finMessageType);
string valorMessageType = messageType.Split('=')[1];
trama.TipoMensaje = TipoMensaje.FijaTipoMensaje(valorMessageType);
// Extraer contenido.
trama.TextoXML = datos.Substring(inicioXML);
log.Debug("Mensaje string con contenido: {contenido}", trama.TextoXML);
return trama;
}
/// <summary>
/// Empaca string de entrada en arreglo de bytes.
/// Agrega encabeado con longitud del mensaje (4 bytes big-endian).
/// No interpreta string de mensaje.
/// <returns>Arreglo bytes con mensaje y cabecera con su longitud en bytes. Mensaje codificado UTF-8.</returns>
/// </summary>
public byte[] Salida(TramaSCO trama)
{
// Codifica longitud del mensaje en los 4 primeros bytes.
byte[] bytesConLongMensaje = BitConverter.GetBytes(trama.Longitud);
// Bytes mas significativos deben ir primero, usa 'big-endian'.
if (BitConverter.IsLittleEndian)
Array.Reverse(bytesConLongMensaje);
Log.Debug("Longitud mensaje: {long} - bytes con longitud: {bytes}", trama.Longitud, bytesConLongMensaje);
// Codifica en bytes texto del mensaje.
byte[] bytesConMensaje = Encoding.UTF8.GetBytes(trama.TextoEncabezado + trama.TextoXML);
Log.Debug("Texto mensaje: '{texto}'", trama.TextoEncabezado + trama.TextoXML);
// Copia los 2 arreglos de bytes en un arreglo unificado.
byte[] bytes = new byte[bytesConLongMensaje.Length + bytesConMensaje.Length];
Buffer.BlockCopy(bytesConLongMensaje, 0, bytes, 0, bytesConLongMensaje.Length);
Buffer.BlockCopy(bytesConMensaje, 0, bytes, bytesConLongMensaje.Length, bytesConMensaje.Length);
if (_isDebug) log.Debug("Buffer salida: >>{bytes}<<", bytes);
return bytes;
}
}
/// <summary>
/// Clase que interpreta los mensajes y los transforma en objetos DTO.
/// </summary>
class Presentacion
{
DirectorioAdaptadoresDTO _adaptadores;
DirectorioCmds _comandos;
public Presentacion(DirectorioAdaptadoresDTO adaptadores, DirectorioCmds comandos)
{
_comandos = comandos;
_adaptadores = adaptadores;
}
/// <summary>
/// Retorna DTO con los datos del mensaje.
/// </summary>
public IComando Entrada(TramaSCO mensaje)
{
IComando cmd;
try
{
XmlElement? docXml = mensaje.ContenidoXML.DocumentElement;
if (docXml == null)
throw new Exception("Contenido XML vacío.");
XmlNode? nodoRaiz = docXml.SelectSingleNode(".");
if (nodoRaiz == null)
throw new Exception("Contenido XML vacío.");
Log.Debug("Mensaje, contenido xml: '{nodoInicial}'", Util.ContenidoXmlComoString(nodoRaiz));
// Según el elemento XML raíz, se determina el comando y dto adecuados.
// Se puede considerar incluir en el comando pasar los datos de mensaje
// directamente, sin embargo, esto no se hace así: el comando solo debe
// depender de los datos pasados en una DTO. De esta manera, el comando
// es independizado del formato del mensaje.
// Al adaptador se encarga de tranformar los datos del mensaje en
// valores inicializados en el DTO.
cmd = _comandos.ObtieneComando(nodoRaiz.Name);
IAdaptadorDTO adaptador = _adaptadores.ObtieneAdaptador(nodoRaiz.Name);
DTOBase dto = adaptador.ObtieneDTO(mensaje.IdSesion, mensaje.TipoMensaje, docXml);
cmd.CargaDTO(dto);
}
catch (Exception e)
{
Log.Error("Excepción procesando Mensaje XML: {e}", e.Message);
ErrorDTO dto = new ErrorDTO(mensaje.IdSesion, mensaje.TipoMensaje, $"Mensaje XML con valor nulo o no reconocido: '{e.Message}'");
cmd = new ErrorCmd();
cmd.CargaDTO(dto);
}
return cmd;
}
}
}
using System;
using System.IO;
using System.Xml;
namespace EvaPosUtil
{
/// <summary>
/// Clase Utilistarios para operaciones sobre mensajes.
/// </summary>
public class Util
{
/// <summary>
/// Utilitario para convertir en string contenido XML de objeto XmlNode.
/// </summary>
public static string ContenidoXmlComoString(XmlNode docXml)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
settings.NewLineOnAttributes = true;
using (var stringWriter = new StringWriter())
using (var xmlTextWriter = XmlWriter.Create(stringWriter, settings))
{
docXml.WriteTo(xmlTextWriter);
xmlTextWriter.Flush();
return stringWriter.GetStringBuilder().ToString();
}
}
/// <summary>
/// Lee valor int de entidad XML indicada por xpath..
/// </summary>
public static int LeeIntEnNodo(XmlNode nodo, string xpath)
{
int valor = 0;
XmlNode? xnodo = nodo.SelectSingleNode(xpath);
if (xnodo == null)
throw new Exception($"Nodo <{nodo.Name}> no contiene a <{xpath}>.");
string? xvalor = xnodo.InnerText;
if (xvalor == null)
throw new Exception($"Nodo <{xpath}> con contenido nulo.");
try
{
valor = int.Parse(xvalor);
}
catch (Exception e)
{
throw new Exception($"Error '{e.Message}' convirtiendo valor <{xvalor}> de <{xpath}> en entero.");
}
return valor;
}
/// <summary>
/// Lee valor long de entidad XML indicada por xpath..
/// </summary>
public static long LeeLongEnNodo(XmlNode nodo,string xpath)
{
long valor = 0;
XmlNode? xnodo = nodo.SelectSingleNode(xpath);
if (xnodo == null)
throw new Exception($"Nodo <{nodo.Name}> no contiene a <{xpath}>.");
string? xvalor = xnodo.InnerText;
if (xvalor == null)
throw new Exception($"Nodo <{xpath}> con contenido nulo.");
try
{
valor = long.Parse(xvalor);
}
catch (Exception e)
{
throw new Exception($"Error '{e.Message}' convirtiendo valor <{xvalor}> de <{xpath}> en entero.");
}
return valor;
}
/// <summary>
/// Lee valor bool de entidad XML indicada por xpath.
/// </summary>
public static bool LeeBoolEnNodo(XmlNode nodo, string xpath)
{
bool valor = false;
XmlNode? xnodo = nodo.SelectSingleNode(xpath);
if (xnodo == null)
throw new Exception($"Nodo '{nodo.Name}' no contiene a '{xpath}'.");
string? xvalor = xnodo.InnerText;
if (xvalor == null)
throw new Exception($"Nodo '{xpath}' con contenido nulo.");
else if (xvalor.ToLower() == "false")
valor = false;
else if (xvalor.ToLower() == "true")
valor = true;
else
throw new Exception($"Valor '{xvalor}' de '{xpath}' no se puede convertir a Boolean.");
return valor;
}
/// <summary>
/// Lee valor string de entidad XML indicada por xpath.
/// </summary>
public static string LeeStringEnNodo(XmlNode nodo, string xpath)
{
XmlNode? xnode = nodo.SelectSingleNode(xpath);
if (xnode == null)
throw new Exception($"Nodo '{nodo.Name}' no contiene a '{xpath}'.");
string? valor = xnode.InnerText;
if (valor == null)
throw new Exception($"Nodo '{xpath}' con contenido nulo.");
return valor;
}
}
}
\ No newline at end of file
using Serilog;
using static System.Net.Mime.MediaTypeNames;
using EvaPosSCOSrv;
using EvaPosSrvAplicacionImp;
using EvaPosCmdImp;
using EvaPosSrvRespImp;
using System;
/// <summary>
/// EvaPos-API : servidor api, sockets y rest.
/// Usa Serilog para bitácora.
/// </summary>
class Program
{
static void Main(string[] args)
{
Console.WriteLine();
Console.WriteLine("Version: {0}", Environment.Version.ToString());
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("C:\\GK-Gateway\\log\\evapos-api.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
// TODO - opción de incluir la activación en la cadena de configuración.
// TODO - aunque hace uso de una "invocación fluida" no parece ser el tipo de api que se beneficia de ese esquema.
ServidorSocket servidor = new ServidorSocket()
//.ConIp("127.0.0.1")
.ConIp("192.168.101.70")
.EnPuerto(6697)
.AgregaDispensadorAdaptadores(IniciaDirectorioAdaptadores.Cargar().DirectorioAdaptadores)
.AgregaDispensadorComandos(IniciaDirectorioCmds.Cargar().DirectorioCmds)
.AgregaProcesadorAplicacion(new Aplicacion())
.Activa();
}
}
\ No newline at end of file
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML.AddCustomer
{
/// <summary>
/// Respuesta de la petición agregar cliente
/// </summary>
public class AddCustomerResponse : Respuesta
{
public int RequestID { get; set; }
public string CustomerID { get; set; }
public string Name { get; set; }
public long YTDPoints { get; set; }
private string _xml;
public AddCustomerResponse(int sessionId,
TipoMensaje messageType,
int requestID,
string customerID,
string name,
long yTDPoints) : base(sessionId, messageType)
{
RequestID = requestID;
CustomerID = customerID;
Name = name;
YTDPoints = yTDPoints;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:AddCustomerResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<AddCustomerResult>
<RequestID>{RequestID}</RequestID>
<CustomerInfo>
<CustomerID>{CustomerID}</CustomerID>
<Name>{Name}</Name>
<YTDPoints>{YTDPoints}</YTDPoints>
</CustomerInfo>
</AddCustomerResult>
</schema:AddCustomerResponse>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace EvaPOS_API_FRAME.RespuestasXML.AddCustomer
{
/// <summary>
/// Respuesta cuando no se encuentra un cliente
/// </summary>
public class AddCustomerResponseNoEncontrado : Respuesta
{
public int RequestID { get; set; }
public string Message { get; set; }
public string ErrorCode { get; set; }
private string _xml;
public AddCustomerResponseNoEncontrado(int sessionId,
TipoMensaje messageType,
int requestID,
string message,
string errorCode) : base(sessionId, messageType)
{
RequestID = requestID;
Message = message;
ErrorCode = errorCode;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:AddCustomerResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<AddCustomerResult>
<RequestID>{RequestID}</RequestID>
<ExceptionResult>
<Message>{Message}</Message>
<ErrorCode>{ErrorCode}</ErrorCode>
</ExceptionResult>
</AddCustomerResult>
</schema:AddCustomerResponse>
""";
}
}
}
using EvaPOS_API_FRAME.DTO;
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML
{
/// <summary>
/// Respuesta a petición de agregar item - (AddItemResponse)
/// </summary>
public class AddItemResponse : Respuesta
{
public int RequestID { get; set; }
public string Description { get; set; }
public string ReturnFlag { get; set; }
public string DepositFlag { get; set; }
public string VoidFlag { get; set; }
public string ItemIdentifier { get; set; }
public string ItemEntryMethod { get; set; }
public string RegularUnitPrice { get; set; }
//Prueba en string, porque en double hay error
public string ExtendedPrice { get; set; }
public int Quantity { get; set; }
public double Weight { get; set; }
public string DealPrice { get; set; }
public int DealQuantity { get; set; }
public int DepartmentNumber { get; set; }
public int MixAndMatchPricingGroup { get; set; }
public string PriceDerivationMethod { get; set; }
public string TimeRestrictedFlag { get; set; }
public int RestrictedAge { get; set; }
public string FoodStampEligibleFlag { get; set; }
public string WICEligibleFlag { get; set; }
public string ItemRepeatAllowedFlag { get; set; }
public string TaxableFlag { get; set; }
private string _xml;
public AddItemResponse(int sessionId,
TipoMensaje messageType,
int requestID,
string description,
string returnFlag,
string depositFlag,
string voidFlag,
string itemIdentifier,
string itemEntryMethod,
string regularUnitPrice,
int quantity,
string extendedPrice,
double weight,
string dealPrice,
int dealQuantity,
int departmentNumber,
int mixAndMatchPricingGroup,
string priceDerivationMethod,
string timeRestrictedFlag,
int restrictedAge,
string foodStampEligibleFlag,
string wICEligibleFlag,
string itemRepeatAllowedFlag,
string taxableFlag) : base(sessionId, messageType)
{
RequestID = requestID;
Description = description;
ReturnFlag = returnFlag;
DepositFlag = depositFlag;
VoidFlag = voidFlag;
ItemIdentifier = itemIdentifier;
ItemEntryMethod = itemEntryMethod;
RegularUnitPrice = regularUnitPrice;
Quantity = quantity;
ExtendedPrice = extendedPrice;
Weight = weight;
DealPrice = dealPrice;
DealQuantity = dealQuantity;
DepartmentNumber = departmentNumber;
MixAndMatchPricingGroup = mixAndMatchPricingGroup;
PriceDerivationMethod = priceDerivationMethod;
TimeRestrictedFlag = timeRestrictedFlag;
RestrictedAge = restrictedAge;
FoodStampEligibleFlag = foodStampEligibleFlag;
WICEligibleFlag = wICEligibleFlag;
ItemRepeatAllowedFlag = itemRepeatAllowedFlag;
TaxableFlag = taxableFlag;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:AddItemResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<AddItemResult>
<RequestID>{RequestID}</RequestID>
<LineItem>
<Description>{Description}</Description>
<ReturnFlag>{ReturnFlag}</ReturnFlag>
<DepositFlag>{DepositFlag}</DepositFlag>
<VoidFlag>{VoidFlag}</VoidFlag>
<ItemIdentifier>{ItemIdentifier}</ItemIdentifier>
<ItemEntryMethod>{ItemEntryMethod}</ItemEntryMethod>
<RegularUnitPrice>{RegularUnitPrice}</RegularUnitPrice>
<Quantity>{Quantity}</Quantity>
<ExtendedPrice>{ExtendedPrice}</ExtendedPrice>
<Weight>{Weight}</Weight>
<DealPrice>{DealPrice}</DealPrice>
<DealQuantity>{DealQuantity}</DealQuantity>
<DepartmentNumber>{DepartmentNumber}</DepartmentNumber>
<MixAndMatchPricingGroup>{MixAndMatchPricingGroup}</MixAndMatchPricingGroup>
<PriceDerivationMethod>{PriceDerivationMethod}</PriceDerivationMethod>
<TimeRestrictedFlag>{TimeRestrictedFlag}</TimeRestrictedFlag>
<RestrictedAge>{RestrictedAge}</RestrictedAge>
<FoodStampEligibleFlag>{FoodStampEligibleFlag}</FoodStampEligibleFlag>
<WICEligibleFlag>{WICEligibleFlag}</WICEligibleFlag>
<ItemRepeatAllowedFlag>{ItemRepeatAllowedFlag}</ItemRepeatAllowedFlag>
<TaxableFlag>{TaxableFlag}</TaxableFlag>
</LineItem>
</AddItemResult>
</schema:AddItemResponse>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML
{
/// <summary>
/// Ultima respuesta cuando se va a pagar con tarjeta de credito
/// </summary>
public class AddTenderDebitResponse : Respuesta
{
public int RequestID { get; set; }
public string ReturnFlag { get; set; }
public string DepositFlag { get; set; }
public string VoidFlag { get; set; }
public string Amount { get; set; }
public string LineItemType { get; set; }
public string BalanceDueSatisfied { get; set; }
public string MaskedAccountNumber { get; set; }
public int ResponseCode { get; set; }
public long ApprovalCode { get; set; }
public string IsDeclined { get; set; }
public string ResponseCodeDescriptor { get; set; }
public long SequenceNumber { get; set; }
public int CashBackAmount { get; set; }
public string SignatureNeeded { get; set; }
private string _xml;
public AddTenderDebitResponse(int sessionId,
TipoMensaje messageType,
int requestID,
string returnFlag,
string depositFlag,
string voidFlag,
string amount,
string lineItemType,
string balanceDueSatisfied,
string maskedAccountNumber,
int responseCode,
long approvalCode,
long sequenceNumber,
int cashBackAmount,
string isDeclined,
string responseCodeDescriptor,
string signatureNeeded) : base(sessionId, messageType)
{
RequestID = requestID;
ReturnFlag = returnFlag;
DepositFlag = depositFlag;
VoidFlag = voidFlag;
Amount = amount;
LineItemType = lineItemType;
BalanceDueSatisfied = balanceDueSatisfied;
MaskedAccountNumber = maskedAccountNumber;
ResponseCode = responseCode;
ApprovalCode = approvalCode;
IsDeclined = isDeclined;
ResponseCodeDescriptor = responseCodeDescriptor;
SequenceNumber = sequenceNumber;
CashBackAmount = cashBackAmount;
SignatureNeeded = signatureNeeded;
}
public override string TextoXML
{
//get => _xml = $"""
// <?xml version="1.0" encoding="UTF-8"?>
// <schema:AddTenderResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
// <AddTenderResult>
// <RequestID>{RequestID}</RequestID>
// <DebitInfo>
// <ReturnFlag>{ReturnFlag}</ReturnFlag>
// <DepositFlag>{DepositFlag}</DepositFlag>
// <VoidFlag>{VoidFlag}</VoidFlag>
// <Amount>{Amount}</Amount>
// <LineItemType>{LineItemType}</LineItemType>
// <BalanceDueSatisfied>{BalanceDueSatisfied}</BalanceDueSatisfied>
// <LegalText>____________________________________</LegalText>
// <LegalText>Acepto pagar el importe total arriba indicado</LegalText>
// <LegalText>según el acuerdo con el emisor de la tarjeta</LegalText>
// <ReferenceNumber></ReferenceNumber>
// <MaskedAccountNumber>************{MaskedAccountNumber}</MaskedAccountNumber>
// <ResponseCode>{ResponseCode}</ResponseCode>
// <ApprovalCode>{ApprovalCode}</ApprovalCode>
// <SequenceNumber>{SequenceNumber}</SequenceNumber>
// <CashBackAmount>{CashBackAmount}</CashBackAmount>
// <IsDeclined>{IsDeclined}</IsDeclined>
// <ResponseCodeDescriptor>00 {ResponseCodeDescriptor}</ResponseCodeDescriptor>
// </DebitInfo>
// <SignatureNeeded>{SignatureNeeded}</SignatureNeeded>
// </AddTenderResult>
// </schema:AddTenderResponse>
// """;
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:AddTenderResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<AddTenderResult>
<RequestID>{RequestID}</RequestID>
<DebitInfo>
<ReturnFlag>{ReturnFlag}</ReturnFlag>
<DepositFlag>{DepositFlag}</DepositFlag>
<VoidFlag>{VoidFlag}</VoidFlag>
<Amount>{Amount}</Amount>
<LineItemType>{LineItemType}</LineItemType>
<BalanceDueSatisfied>{BalanceDueSatisfied}</BalanceDueSatisfied>
<LegalText> ____________________________________ </LegalText>
<LegalText>I agree to pay the above total amount </LegalText>
<LegalText>according to the card issuer agreement</LegalText>
<ReferenceNumber />
<MaskedAccountNumber>************{MaskedAccountNumber}</MaskedAccountNumber>
<ResponseCode>-1</ResponseCode>
<ApprovalCode>{ApprovalCode}</ApprovalCode>
<SequenceNumber>{SequenceNumber}</SequenceNumber>
<CashBackAmount>{CashBackAmount}</CashBackAmount>
<IsDeclined>{IsDeclined}</IsDeclined>
<ResponseCodeDescriptor>00 {ResponseCodeDescriptor} </ResponseCodeDescriptor>
</DebitInfo>
<SignatureNeeded>{SignatureNeeded}</SignatureNeeded>
</AddTenderResult>
</schema:AddTenderResponse>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML
{
public class AddTenderResponseError : Respuesta
{
public int RequestID { get; set; }
public string Message { get; set; }
public string ErrorCode { get; set; }
private string _xml;
public AddTenderResponseError(int sessionId,
TipoMensaje messageType,
int requestID,
string message,
string errorCode) : base(sessionId, messageType)
{
RequestID = requestID;
Message = message;
ErrorCode = errorCode;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:AddTenderResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<AddTenderResult>
<RequestID>{RequestID}</RequestID>
<ExceptionResult>
<Message>{Message}</Message>
<ErrorCode>{ErrorCode}</ErrorCode>
</ExceptionResult>
</AddTenderResult>
</schema:AddTenderResponse>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML.CancelDatafono
{
/// <summary>
/// Respuesta cuando se cancela la transaccion por datafono
/// </summary>
public class CancelActionResponse : Respuesta
{
public int RequestID { get; set; }
private string _xml;
public CancelActionResponse(int sessionId,
TipoMensaje messageType,
int requestID) : base(sessionId, messageType)
{
RequestID = requestID;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:CancelActionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<CancelActionResult>
<RequestID>{RequestID}</RequestID>
<ExceptionResult>
<Message xsi:nil="true"/>
<ErrorCode>UNSUPPORTED_OPERATION</ErrorCode>
</ExceptionResult>
</CancelActionResult>
</schema:CancelActionResponse>
""";
}
}
}
using EvaPOS_API_FRAME.DTO;
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML.CancelTransaction
{
/// <summary>
/// Respuesta a petición de cancelar transacción
/// </summary>
public class VoidTransactionResponse : Respuesta
{
public int RequestID { get; set; }
private string _xml;
public VoidTransactionResponse(int sessionId,
TipoMensaje messageType,
int requestID) : base(sessionId, messageType)
{
RequestID = requestID;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:VoidTransactionResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<VoidTransactionResult>
<RequestID>{RequestID}</RequestID>
</VoidTransactionResult>
</schema:VoidTransactionResponse>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML
{
/// <summary>
/// Respuesta a petición de finalizar y pagar, cuarta respuesta
/// </summary>
public class GetTotalsResponse : Respuesta
{
public int RequestID { get; set; }
public string Total { get; set; }
public string SubTotal { get; set; }
public string Tax { get; set; }
public string BalanceDue { get; set; }
public string ChangeDue { get; set; }
public string FoodstampChangeDue { get; set; }
public string FoodstampTotal { get; set; }
public string FoodstampBalanceDue { get; set; }
public string CouponTotal { get; set; }
public int TotalItems { get; set; }
public int TotalCoupons { get; set; }
public string TotalSavings { get; set; }
public string TenderApplied { get; set; }
private string _xml;
public GetTotalsResponse(int sessionId,
TipoMensaje messageType,
int requestID,
string total,
string subtotal,
string tax,
string balanceDue,
string changeDue,
string foodstampChangeDue,
string foodstampTotal,
string foodstampBalanceDue,
string couponTotal,
int totalItems,
int totalCoupons,
string totalSavings,
string tenderApplied) : base(sessionId, messageType)
{
RequestID = requestID;
Total = total;
SubTotal = subtotal;
Tax = tax;
BalanceDue = balanceDue;
ChangeDue = changeDue;
FoodstampChangeDue = foodstampChangeDue;
FoodstampTotal = foodstampTotal;
FoodstampBalanceDue = foodstampBalanceDue;
CouponTotal = couponTotal;
TotalItems = totalItems;
TotalCoupons = totalCoupons;
TotalSavings = totalSavings;
TenderApplied = tenderApplied;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:GetTotalsResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<GetTotalsResult>
<RequestID>{RequestID}</RequestID>
<TransactionTotals>
<Total>{Total}</Total>
<SubTotal>{SubTotal}</SubTotal>
<Tax>{Tax}</Tax>
<BalanceDue>{BalanceDue}</BalanceDue>
<ChangeDue>{ChangeDue}</ChangeDue>
<FoodstampChangeDue>{FoodstampChangeDue}</FoodstampChangeDue>
<FoodstampTotal>{FoodstampTotal}</FoodstampTotal>
<FoodstampBalanceDue>{FoodstampBalanceDue}</FoodstampBalanceDue>
<CouponTotal>{CouponTotal}</CouponTotal>
<TotalItems>{TotalItems}</TotalItems>
<TotalCoupons>{TotalCoupons}</TotalCoupons>
<TotalSavings>{TotalSavings}</TotalSavings>
<TenderApplied>{TenderApplied}</TenderApplied>
</TransactionTotals>
</GetTotalsResult>
</schema:GetTotalsResponse>
""";
//get => _xml = $"""
// <?xml version="1.0" encoding="UTF-8"?>
// <schema:GetTotalsResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
// <GetTotalsResult>
// <RequestID>104</RequestID>
// <TransactionTotals>
// <Total>3.99</Total>
// <SubTotal>3.99</SubTotal>
// <Tax>0.00</Tax>
// <BalanceDue>3.99</BalanceDue>
// <ChangeDue>0.00</ChangeDue>
// <FoodstampChangeDue>0.00</FoodstampChangeDue>
// <FoodstampTotal>0.00</FoodstampTotal>
// <FoodstampBalanceDue>0.00</FoodstampBalanceDue>
// <CouponTotal>0.00</CouponTotal>
// <TotalItems>1</TotalItems>
// <TotalCoupons>0</TotalCoupons>
// <TotalSavings>0.00</TotalSavings>
// <TenderApplied>0.00</TenderApplied>
// </TransactionTotals>
// </GetTotalsResult>
// </schema:GetTotalsResponse>
// """;
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML
{
/// <summary>
/// Respuesta a petición de inicialización de POS.
/// </summary>
public class InitializeResponse : Respuesta
{
public int RequestID { get; private set; }
private string _xml;
public InitializeResponse(int sessionId,
TipoMensaje messageType,
int requestID) : base(sessionId, messageType)
{
RequestID = requestID;
}
// P2L_3_POSBCStatusEvent
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:InitializeResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<InitializeResult>
<RequestID>{RequestID}</RequestID>
</InitializeResult>
</schema:InitializeResponse>
""";
}
}
}
using EvaPOS_API_FRAME.DTO;
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML
{
/// <summary>
/// Respuesta a petición de agregar item - aca se manejan los errores de los productos
/// producto con cantidad, sin cupon ,no encontrado y sin precio
/// </summary>
public class AddItemResponseError : Respuesta
{
public int RequestID { get; set; }
public string Message { get; set; }
public string ErrorCode { get; set; }
private string _xml;
public AddItemResponseError(int sessionId,
TipoMensaje messageType,
int requestID,
string message,
string errorCode): base(sessionId,messageType)
{
RequestID = requestID;
Message = message;
ErrorCode = errorCode;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:AddItemResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<AddItemResult>
<RequestID>{RequestID}</RequestID>
<ExceptionResult>
<Message>{Message}</Message>
<ErrorCode>{ErrorCode}</ErrorCode>
</ExceptionResult>
</AddItemResult>
</schema:AddItemResponse>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML
{
/// <summary>
/// Evento de respuesta.
/// </summary>
public class POSBCStatusEvent : Respuesta
{
public string Severity { get; private set; }
public string Status { get; private set; }
public string Message { get; private set; }
private string _xml;
public POSBCStatusEvent(int sessionId,
TipoMensaje messageType,
string severity,
string status,
string message) : base(sessionId, messageType)
{
Severity = severity;
Status = status;
Message = message;
}
// P2L_3_POSBCStatusEvent
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:POSBCStatusEvent xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<POSBCStatus>
<Severity>{Severity}</Severity>
<Status>{Status}</Status>
<StatusMessage>{Message}</StatusMessage>
</POSBCStatus>
</schema:POSBCStatusEvent>
""";
}
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment