Payments Service
1. Descripción
Microservicio responsable del procesamiento de transacciones, integración con pasarelas de pago y gestión del ciclo de vida de pagos. Implementa el agregado Pago según el lenguaje ubicuo.
Bounded Context: Pagos y Facturación
Repository: eventmesh-lab/payments-service
2. Responsabilidades
- Procesar pagos de publicación de eventos
- Procesar pagos de compra de tickets
- Integrar con pasarelas de pago externas (Stripe, PayPal simulado)
- Gestionar reintentos automáticos ante fallos
- Confirmar o rechazar transacciones
- Emitir eventos de dominio para coordinación con otros servicios
- Registrar auditoría completa de transacciones
3. Modelo de Dominio
3.1 Agregado: Pago
Root Aggregate: Pago
Entidades
Pago
public class Pago : AggregateRoot
{
public Guid Id { get; private set; }
public Monto Monto { get; private set; }
public MetodoPago MetodoPago { get; private set; }
public EstadoPago Estado { get; private set; }
public Guid UsuarioId { get; private set; }
public TipoPago Tipo { get; private set; }
public string ReferenciaExterna { get; private set; } // ID de la pasarela
public Guid? EventoId { get; private set; }
public Guid? ReservaId { get; private set; }
public DateTime FechaCreacion { get; private set; }
public DateTime? FechaConfirmacion { get; private set; }
public int IntentosRealizados { get; private set; }
public string MensajeError { get; private set; }
}
Value Objects
Monto
public record Monto
{
public decimal Valor { get; init; }
public string Moneda { get; init; } // USD, EUR, MXN
// Validaciones: Valor debe ser positivo
}
EstadoPago
public enum EstadoPago
{
Pendiente, // Pago iniciado, esperando procesamiento
Confirmado, // Pago exitoso
Fallido, // Pago rechazado por la pasarela
Reembolsado, // Pago devuelto al cliente
Procesando // En proceso de validación
}
MetodoPago
public enum MetodoPago
{
TarjetaCredito,
TarjetaDebito,
PayPal,
Transferencia,
Efectivo
}
TipoPago
public enum TipoPago
{
PagoPublicacion, // Tarifa para publicar evento
PagoEntradas // Compra de tickets
}
4. Comandos del Dominio
IniciarPago
Descripción: Inicia un nuevo proceso de pago.
Input:
public record IniciarPagoCommand
{
public decimal Monto { get; init; }
public string Moneda { get; init; }
public Guid UsuarioId { get; init; }
public MetodoPago MetodoPago { get; init; }
public TipoPago Tipo { get; init; }
public Guid? EventoId { get; init; }
public Guid? ReservaId { get; init; }
}
Validaciones:
- El monto debe ser mayor a 0
- El usuario debe existir
- Si es PagoPublicacion, debe existir el evento
- Si es PagoEntradas, debe existir la reserva
Emite: PagoIniciado
Estado resultante: Procesando
ConfirmarPago
Descripción: Marca el pago como confirmado tras respuesta exitosa de la pasarela.
Input:
public record ConfirmarPagoCommand
{
public Guid PagoId { get; init; }
public string ReferenciaExterna { get; init; }
}
Validaciones:
- El pago debe estar en estado Procesando
- La referencia externa debe ser válida
Emite: PagoConfirmado
Estado resultante: Confirmado
FallarPago
Descripción: Marca el pago como fallido y programa reintentos si aplica.
Input:
public record FallarPagoCommand
{
public Guid PagoId { get; init; }
public string Razon { get; init; }
}
Validaciones:
- El pago debe estar en estado Procesando
Emite: PagoFallido
Estado resultante: Fallido
Lógica de reintentos: - Hasta 3 reintentos automáticos con backoff exponencial (1min, 5min, 15min) - Después del tercer fallo, notifica al usuario
ReembolsarPago
Descripción: Procesa el reembolso de un pago confirmado.
Input:
public record ReembolsarPagoCommand
{
public Guid PagoId { get; init; }
public string Motivo { get; init; }
}
Validaciones:
- El pago debe estar en estado Confirmado
- No debe haberse reembolsado previamente
Emite: PagoReembolsado
Estado resultante: Reembolsado
5. Eventos de Dominio
PagoIniciado
public record PagoIniciado : DomainEvent
{
public Guid PagoId { get; init; }
public decimal Monto { get; init; }
public TipoPago Tipo { get; init; }
public Guid UsuarioId { get; init; }
}
PagoConfirmado
public record PagoConfirmado : DomainEvent
{
public Guid PagoId { get; init; }
public Guid UsuarioId { get; init; }
public TipoPago Tipo { get; init; }
public Guid? EventoId { get; init; }
public Guid? ReservaId { get; init; }
public decimal Monto { get; init; }
public DateTime FechaConfirmacion { get; init; }
}
Suscriptores:
- events-service: Publica evento si es PagoPublicacion
- tickets-service: Confirma tickets si es PagoEntradas
- invoicing-service: Genera factura
- notifications-service: Notifica al usuario
PagoFallido
public record PagoFallido : DomainEvent
{
public Guid PagoId { get; init; }
public string Razon { get; init; }
public int IntentosRealizados { get; init; }
}
Suscriptores:
- notifications-service: Notifica al usuario
- reservations-service: Puede cancelar reserva si excede reintentos
PagoReembolsado
public record PagoReembolsado : DomainEvent
{
public Guid PagoId { get; init; }
public Guid UsuarioId { get; init; }
public decimal Monto { get; init; }
public string Motivo { get; init; }
}
Suscriptores:
- tickets-service: Cancela tickets
- notifications-service: Notifica reembolso exitoso
6. Reglas de Negocio
-
Transacción atómica: Un pago solo puede estar en un estado a la vez.
-
Idempotencia: Múltiples llamadas con el mismo
PagoIdno deben crear pagos duplicados. -
Reintentos limitados: Máximo 3 intentos automáticos antes de marcar como fallido definitivo.
-
Auditoría completa: Todos los cambios de estado y respuestas de pasarela deben quedar registrados.
-
Timeout de procesamiento: Si un pago queda en estado
Procesandopor más de 10 minutos, se marca como fallido. -
Reembolso parcial no soportado: Solo se permiten reembolsos totales en la versión inicial.
7. Servicios de Dominio
ServicioDePagos
public interface IServicioDePagos
{
Task<Result<string>> ProcesarPagoConPasarela(Guid pagoId, MetodoPago metodo);
Task<Result> ReintentarPagoFallido(Guid pagoId);
Task<Result> ProcesarReembolso(Guid pagoId, string motivo);
}
8. Integración con Pasarelas de Pago
Adaptador para Stripe (Simulado)
public interface IPasarelaPago
{
Task<TransaccionResultado> ProcesarPago(PagoRequest request);
Task<TransaccionResultado> ConsultarEstado(string referenciaExterna);
Task<TransaccionResultado> ProcesarReembolso(string referenciaExterna, decimal monto);
}
Simulación: En ambiente de desarrollo, se simula respuesta exitosa con probabilidad del 90% y fallo del 10%.
9. Jobs Programados (Hangfire)
ReintentarPagosFallidos
- Frecuencia: Cada 5 minutos
- Descripción: Busca pagos en estado
Fallidocon menos de 3 intentos y los reprocesa
LimpiarPagosExpirados
- Frecuencia: Cada hora
- Descripción: Marca como fallidos los pagos en estado
Procesandopor más de 10 minutos
10. Integraciones
Comunicación Asíncrona (RabbitMQ)
Exchange: payments.domain.events
Publica:
- PagoConfirmado
- PagoFallido
- PagoReembolsado
Consume:
- PagoPublicacionIniciado (desde events-service)
- ReservaCreada (desde reservations-service)
11. Persistencia
Base de Datos: PostgreSQL
Tabla: pagos
CREATE TABLE pagos (
id UUID PRIMARY KEY,
monto DECIMAL(10,2) NOT NULL,
moneda VARCHAR(3) NOT NULL,
metodo_pago VARCHAR(50) NOT NULL,
estado VARCHAR(20) NOT NULL,
usuario_id UUID NOT NULL,
tipo VARCHAR(50) NOT NULL,
referencia_externa VARCHAR(200),
evento_id UUID,
reserva_id UUID,
fecha_creacion TIMESTAMP NOT NULL,
fecha_confirmacion TIMESTAMP,
intentos_realizados INT DEFAULT 0,
mensaje_error TEXT,
version INT NOT NULL
);
CREATE INDEX idx_pagos_usuario ON pagos(usuario_id);
CREATE INDEX idx_pagos_estado ON pagos(estado);
CREATE INDEX idx_pagos_evento ON pagos(evento_id);
CREATE INDEX idx_pagos_reserva ON pagos(reserva_id);
Tabla: auditoria_pagos
CREATE TABLE auditoria_pagos (
id UUID PRIMARY KEY,
pago_id UUID NOT NULL REFERENCES pagos(id),
estado_anterior VARCHAR(20),
estado_nuevo VARCHAR(20) NOT NULL,
fecha TIMESTAMP NOT NULL,
detalles TEXT,
usuario_responsable UUID
);
12. API Endpoints
POST /api/pagos
Inicia un nuevo pago.
Request:
{
"monto": 50.00,
"moneda": "USD",
"usuarioId": "uuid",
"metodoPago": "TarjetaCredito",
"tipo": "PagoEntradas",
"reservaId": "uuid"
}
Response: 201 Created
{
"pagoId": "uuid",
"estado": "Procesando",
"fechaCreacion": "2025-11-10T10:30:00Z"
}
GET /api/pagos/{id}
Consulta el estado de un pago.
POST /api/pagos/{id}/confirmar
Endpoint interno para confirmar pago (callback de pasarela).
POST /api/pagos/{id}/reembolsar
Solicita el reembolso de un pago.
GET /api/pagos/usuario/{usuarioId}
Lista todos los pagos de un usuario.
13. Tecnologías
- .NET 8 (Minimal APIs)
- Entity Framework Core 8 (PostgreSQL)
- MediatR (CQRS)
- Hangfire (Jobs de reintento)
- Polly (Políticas de resiliencia)
- RabbitMQ.Client
- Serilog
14. Observabilidad
Métricas
pagos_iniciados_total: Contador de pagos iniciadospagos_confirmados_total: Contador de pagos exitosospagos_fallidos_total: Contador de pagos fallidostiempo_procesamiento_pago_ms: Latencia de procesamiento
Logs
PagoIniciado: Nivel INFOPagoConfirmado: Nivel INFOPagoFallido: Nivel WARNINGErrorPasarela: Nivel ERROR