using Fitness_core_BE.Exceptions; using Fitness_core_BE.Helpers; using Fitness_core_BE.Infra.UoW; using Fitness_core_BE.Interfaces; using Fitness_core_BE.DTOs.PaymentsEnablement; using Fitness_core_BE.Interfaces.Services; using Fitness_core_BE.Models.Enums; namespace Fitness_core_BE.Services { public class PayoutKycReviewService : IPayoutKycReviewService { private readonly IPaymentsEnablementRepository _paymentsRepository; private readonly UserRoleHelper _userRoleHelper; private readonly IUnitOfWork _uow; public PayoutKycReviewService(IPaymentsEnablementRepository paymentsRepository, UserRoleHelper userRoleHelper, IUnitOfWork uow) { _paymentsRepository = paymentsRepository; _userRoleHelper = userRoleHelper; _uow = uow; } public async Task> ListAsync(string? status) { var (items, _) = await _paymentsRepository.ListKycAsync(null); var latestPerClient = items .GroupBy(item => item.FKClientId) .Select(group => group .OrderByDescending(item => item.SubmittedAt) .ThenByDescending(item => item.Id) .First()); if (!string.IsNullOrWhiteSpace(status)) { if (!Enum.TryParse(status, true, out var parsedStatus)) throw new BadRequestException($"Unknown KYC status '{status}'."); latestPerClient = latestPerClient .Where(item => item.KycStatus == parsedStatus); } return latestPerClient .OrderByDescending(item => item.SubmittedAt) .Select(ToDto) .ToList(); } public async Task GetByClientIdAsync(Guid clientId) { var kyc = await _paymentsRepository.GetKycByClientIdAsync(clientId) ?? throw new NotFoundException("No KYC submission found for this client."); return ToDto(kyc); } public async Task ReviewAsync(Guid clientId, ReviewPayoutKycRequest request, HttpContext httpContext) { var kyc = await _paymentsRepository.GetKycForUpdateAsync(clientId) ?? throw new NotFoundException("No KYC submission found for this client."); if (!Enum.TryParse(request.Status, true, out var newStatus)) throw new BadRequestException($"Unknown KYC status '{request.Status}'."); if (newStatus is not KycStatus.Approved and not KycStatus.Rejected) throw new BadRequestException("KYC can only be approved or rejected."); if (kyc.KycStatus != KycStatus.Pending) throw new BadRequestException("Only pending KYC submissions can be reviewed."); if (newStatus == KycStatus.Rejected && string.IsNullOrWhiteSpace(request.Reason)) throw new BadRequestException("A rejection reason is required."); var adminUserId = DataExtractor.GetUserIdFromContext(httpContext); var adminId = await _userRoleHelper.GetAdminId(adminUserId) ?? throw new ForbiddenException("Only admins may review payout KYC."); await _uow.BeginAsync(); try { kyc.KycStatus = newStatus; kyc.ReviewedAt = DateTime.UtcNow; kyc.ReviewedByAdminId = adminId; kyc.RejectionReason = newStatus == KycStatus.Rejected ? request.Reason!.Trim() : null; await _uow.CommitAsync(); return ToDto(kyc); } catch { await _uow.RollbackAsync(); throw; } } // NIC fields used to come from the multi-gym onboarding wizard's OwnerActivation record // (collected during signup); that flow no longer exists for a single already-real club, // so these are always null here now. If gateway KYC needs NIC info going forward, collect // it directly on the KYC submission instead of reintroducing that dependency. private static ClientPayoutKycDto ToDto(Models.ClientPayoutKyc kyc) { return new ClientPayoutKycDto( kyc.Id, kyc.FKClientId, kyc.Client?.ClientName, kyc.SelectedGatewayId, kyc.SelectedGatewayName, kyc.KycStatus.ToString(), kyc.SubmittedAt, kyc.ReviewedAt, kyc.RejectionReason, null, null, null); } } }