using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using RacePlannerApi.Data; using RacePlannerApi.DTOs; using RacePlannerApi.Models; namespace RacePlannerApi.Controllers; [ApiController] [Route("api/[controller]")] [Authorize] public class PaymentsController : ControllerBase { private readonly RacePlannerDbContext _context; public PaymentsController(RacePlannerDbContext context) { _context = context; } [HttpPost] [Authorize(Roles = "Organizer")] public async Task> RecordPayment(CreatePaymentRequest request) { var userId = GetCurrentUserId(); if (userId == null) { return Unauthorized(); } // Load registration with event var registration = await _context.Registrations .Include(r => r.Event) .FirstOrDefaultAsync(r => r.Id == request.RegistrationId); if (registration == null) { return NotFound(new { error = "Registration not found" }); } // Verify user is organizer of the event if (registration.Event.OrganizerId != userId.Value) { return Forbid(); } // Check registration is not cancelled if (registration.Status == RegistrationStatus.Cancelled) { return BadRequest(new { error = "Cannot record payment for cancelled registration" }); } var payment = new Payment { RegistrationId = request.RegistrationId, Amount = request.Amount, Method = request.Method, TransactionId = request.TransactionId, Notes = request.Notes, PaymentDate = request.PaymentDate ?? DateTime.UtcNow }; _context.Payments.Add(payment); await _context.SaveChangesAsync(); // Update registration status if fully paid (assuming a fixed fee amount would be known) // For now, we'll just record the payment return CreatedAtAction( nameof(GetPayment), new { id = payment.Id }, MapToPaymentDto(payment)); } [HttpGet("{id}")] [Authorize(Roles = "Organizer")] public async Task> GetPayment(Guid id) { var userId = GetCurrentUserId(); if (userId == null) { return Unauthorized(); } var payment = await _context.Payments .Include(p => p.Registration) .ThenInclude(r => r.Event) .FirstOrDefaultAsync(p => p.Id == id); if (payment == null) { return NotFound(); } // Verify user is organizer if (payment.Registration.Event.OrganizerId != userId.Value) { return Forbid(); } return Ok(MapToPaymentDto(payment)); } [HttpGet("registration/{registrationId}")] [Authorize(Roles = "Organizer")] public async Task>> GetRegistrationPayments(Guid registrationId) { var userId = GetCurrentUserId(); if (userId == null) { return Unauthorized(); } // Verify organizer has access to this registration var registration = await _context.Registrations .Include(r => r.Event) .FirstOrDefaultAsync(r => r.Id == registrationId); if (registration == null) { return NotFound(); } if (registration.Event.OrganizerId != userId.Value) { return Forbid(); } var payments = await _context.Payments .Where(p => p.RegistrationId == registrationId) .OrderByDescending(p => p.PaymentDate) .ToListAsync(); return Ok(payments.Select(MapToPaymentDto)); } [HttpGet("registration/{registrationId}/status")] [Authorize] public async Task> GetPaymentStatus(Guid registrationId) { var userId = GetCurrentUserId(); if (userId == null) { return Unauthorized(); } var registration = await _context.Registrations .Include(r => r.Event) .Include(r => r.Payments) .FirstOrDefaultAsync(r => r.Id == registrationId); if (registration == null) { return NotFound(); } // Participant can view their own, organizer can view any var isOrganizer = registration.Event.OrganizerId == userId.Value; if (registration.ParticipantId != userId.Value && !isOrganizer) { return Forbid(); } var totalPaid = registration.Payments.Sum(p => p.Amount); var status = totalPaid > 0 ? "paid" : "unpaid"; return Ok(new { RegistrationId = registrationId, TotalPaid = totalPaid, PaymentCount = registration.Payments.Count, Status = status, Payments = registration.Payments.Select(MapToPaymentDto) }); } [HttpGet("event/{eventId}/report")] [Authorize(Roles = "Organizer")] public async Task> GetPaymentReport(Guid eventId) { var userId = GetCurrentUserId(); if (userId == null) { return Unauthorized(); } var eventEntity = await _context.Events .FirstOrDefaultAsync(e => e.Id == eventId && e.OrganizerId == userId.Value); if (eventEntity == null) { return NotFound(); } var registrations = await _context.Registrations .Include(r => r.Participant) .Include(r => r.Payments) .Where(r => r.EventId == eventId && r.Status != RegistrationStatus.Cancelled) .ToListAsync(); var totalCollected = registrations.Sum(r => r.Payments.Sum(p => p.Amount)); var totalRegistrations = registrations.Count; var paidRegistrations = registrations.Count(r => r.Payments.Any()); var unpaidRegistrations = registrations.Count(r => !r.Payments.Any()); var report = new PaymentReportDto { EventId = eventId, EventName = eventEntity.Name, TotalCollected = totalCollected, TotalPending = 0, // Would require event fee structure TotalOutstanding = 0, // Would require event fee structure TotalRegistrations = totalRegistrations, PaidRegistrations = paidRegistrations, PartialRegistrations = 0, // Would require partial payment logic UnpaidRegistrations = unpaidRegistrations, Registrations = registrations.Select(r => new RegistrationPaymentDto { RegistrationId = r.Id, ParticipantName = r.Participant.Name, Status = r.Status.ToString(), TotalPaid = r.Payments.Sum(p => p.Amount), AmountDue = 0, // Would require event fee structure PaymentStatus = r.Payments.Any() ? "paid" : "unpaid", Payments = r.Payments.Select(MapToPaymentDto).ToList() }).ToList() }; return Ok(report); } private Guid? GetCurrentUserId() { var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; if (Guid.TryParse(userIdClaim, out var userId)) { return userId; } return null; } private static PaymentDto MapToPaymentDto(Payment payment) { return new PaymentDto { Id = payment.Id, RegistrationId = payment.RegistrationId, Amount = payment.Amount, Method = payment.Method.ToString(), TransactionId = payment.TransactionId, Notes = payment.Notes, PaymentDate = payment.PaymentDate, CreatedAt = payment.CreatedAt }; } }