258 lines
7.9 KiB
C#
258 lines
7.9 KiB
C#
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<ActionResult<PaymentDto>> 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<ActionResult<PaymentDto>> 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<ActionResult<IEnumerable<PaymentDto>>> 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<ActionResult<object>> 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<ActionResult<PaymentReportDto>> 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
|
|
};
|
|
}
|
|
} |