188 lines
8.0 KiB
C#
188 lines
8.0 KiB
C#
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.Interfaces.Services;
|
|
using Fitness_core_BE.DTOs.PaymentsEnablement;
|
|
using Fitness_core_BE.DTOs.PaymentGateways;
|
|
using Fitness_core_BE.Models;
|
|
using Fitness_core_BE.Models.Enums;
|
|
|
|
namespace Fitness_core_BE.Services
|
|
{
|
|
public class PaymentsEnablementService : IPaymentsEnablementService
|
|
{
|
|
private readonly IPaymentsEnablementRepository _paymentsRepository;
|
|
private readonly IClientRepository _clientRepository;
|
|
private readonly IPaymentServiceClient _paymentServiceClient;
|
|
private readonly UserRoleHelper _userRoleHelper;
|
|
private readonly IUnitOfWork _uow;
|
|
|
|
public PaymentsEnablementService(
|
|
IPaymentsEnablementRepository paymentsRepository,
|
|
IClientRepository clientRepository,
|
|
IPaymentServiceClient paymentServiceClient,
|
|
UserRoleHelper userRoleHelper,
|
|
IUnitOfWork uow)
|
|
{
|
|
_paymentsRepository = paymentsRepository;
|
|
_clientRepository = clientRepository;
|
|
_paymentServiceClient = paymentServiceClient;
|
|
_userRoleHelper = userRoleHelper;
|
|
_uow = uow;
|
|
}
|
|
|
|
public Task<List<PaymentTrackOption>> GetTrackOptionsAsync()
|
|
{
|
|
return Task.FromResult(new List<PaymentTrackOption>
|
|
{
|
|
new(PaymentTrack.OwnGateway.ToString(), new { description = "Use your own PayHere/Stripe merchant account.", feePercentage = 0 }),
|
|
new(PaymentTrack.HexaFitness.ToString(), new { description = "Use HexaFitness's payment gateway; requires KYC before first payout.", feePercentage = 2.5 })
|
|
});
|
|
}
|
|
|
|
public async Task<PaymentTrackSelectionResult> SelectTrackAsync(HttpContext httpContext, SelectPaymentTrackRequest request)
|
|
{
|
|
var userId = DataExtractor.GetUserIdFromContext(httpContext);
|
|
var clientId = await _userRoleHelper.GetClientId(userId);
|
|
|
|
if (!Enum.TryParse<PaymentTrack>(request.Track, true, out var track))
|
|
throw new BadRequestException($"Unknown payment track '{request.Track}'.");
|
|
|
|
var client = await _clientRepository.GetClientEntityByIdAsync(clientId)
|
|
?? throw new NotFoundException("Client not found.");
|
|
|
|
client.PaymentTrack = track;
|
|
await _uow.SaveChangesAsync();
|
|
|
|
return new PaymentTrackSelectionResult(track.ToString(), track == PaymentTrack.HexaFitness);
|
|
}
|
|
|
|
public async Task<List<GatewayDefinitionDto>> GetAvailableGatewaysAsync(
|
|
CancellationToken ct = default)
|
|
{
|
|
return (await _paymentServiceClient.ListGatewaysAsync(ct))
|
|
.Where(gateway => gateway.IsEnabled)
|
|
.OrderByDescending(gateway => gateway.IsDefault)
|
|
.ThenBy(gateway => gateway.DisplayName)
|
|
.ToList();
|
|
}
|
|
|
|
public async Task<ClientPayoutKycDto> SubmitKycAsync(
|
|
HttpContext httpContext,
|
|
SubmitGatewayOnboardingRequest request,
|
|
CancellationToken ct = default)
|
|
{
|
|
var userId = DataExtractor.GetUserIdFromContext(httpContext);
|
|
var clientId = await _userRoleHelper.GetClientId(userId);
|
|
|
|
var gateway = (await GetAvailableGatewaysAsync(ct))
|
|
.FirstOrDefault(item => item.Id == request.GatewayId)
|
|
?? throw new BadRequestException("Select an available payment gateway.");
|
|
|
|
var client = await _clientRepository.GetClientEntityByIdAsync(clientId)
|
|
?? throw new NotFoundException("Client not found.");
|
|
|
|
var current = await _paymentsRepository.GetKycByClientIdAsync(clientId, ct);
|
|
if (current?.KycStatus == KycStatus.Pending)
|
|
throw new BadRequestException("A gateway KYC request is already pending review.");
|
|
if (current?.KycStatus == KycStatus.Approved)
|
|
throw new BadRequestException("Gateway KYC is already approved for this client.");
|
|
|
|
await _uow.BeginAsync();
|
|
try
|
|
{
|
|
// Catalog gateways are platform-managed, so this follows the
|
|
// HexaFitness payment track rather than client-owned credentials.
|
|
client.PaymentTrack = PaymentTrack.HexaFitness;
|
|
|
|
var kyc = new ClientPayoutKyc
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
FKClientId = clientId,
|
|
SelectedGatewayId = gateway.Id,
|
|
SelectedGatewayName = gateway.DisplayName,
|
|
KycStatus = KycStatus.Pending,
|
|
SubmittedAt = DateTime.UtcNow
|
|
};
|
|
|
|
await _paymentsRepository.CreateKycAsync(kyc, ct);
|
|
await _uow.CommitAsync();
|
|
|
|
return ToDto(kyc, client);
|
|
}
|
|
catch
|
|
{
|
|
await _uow.RollbackAsync();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<GatewayOnboardingStatusDto> GetKycStatusAsync(HttpContext httpContext)
|
|
{
|
|
var userId = DataExtractor.GetUserIdFromContext(httpContext);
|
|
var clientId = await _userRoleHelper.GetClientId(userId);
|
|
var kyc = await _paymentsRepository.GetKycByClientIdAsync(clientId);
|
|
return kyc is null
|
|
? new GatewayOnboardingStatusDto("NotSubmitted", null, null, null)
|
|
: new GatewayOnboardingStatusDto(
|
|
kyc.KycStatus.ToString(),
|
|
kyc.SelectedGatewayId,
|
|
kyc.SelectedGatewayName,
|
|
kyc.RejectionReason);
|
|
}
|
|
|
|
public async Task<WalletDto> GetWalletAsync(HttpContext httpContext)
|
|
{
|
|
var userId = DataExtractor.GetUserIdFromContext(httpContext);
|
|
var clientId = await _userRoleHelper.GetClientId(userId);
|
|
var wallet = await _paymentsRepository.GetWalletByClientIdAsync(clientId)
|
|
?? throw new NotFoundException("Wallet not found for this client.");
|
|
|
|
var ledger = await _paymentsRepository.GetLedgerAsync(wallet.Id);
|
|
|
|
return new WalletDto(
|
|
wallet.Balance,
|
|
wallet.Currency,
|
|
ledger.Select(e => new WalletLedgerEntryDto(e.Id, e.EntryType.ToString(), e.Amount, e.Description, e.CreatedAt)).ToList(),
|
|
wallet.LastPayoutDate);
|
|
}
|
|
|
|
public async Task<PayoutScheduleDto> GetPayoutScheduleAsync(HttpContext httpContext)
|
|
{
|
|
var userId = DataExtractor.GetUserIdFromContext(httpContext);
|
|
var clientId = await _userRoleHelper.GetClientId(userId);
|
|
var wallet = await _paymentsRepository.GetWalletByClientIdAsync(clientId)
|
|
?? throw new NotFoundException("Wallet not found for this client.");
|
|
|
|
var today = DateTime.UtcNow.Date;
|
|
var daysUntilTuesday = ((int)DayOfWeek.Tuesday - (int)today.DayOfWeek + 7) % 7;
|
|
daysUntilTuesday = daysUntilTuesday == 0 ? 7 : daysUntilTuesday;
|
|
var nextPayoutDate = today.AddDays(daysUntilTuesday);
|
|
|
|
return new PayoutScheduleDto(nextPayoutDate, "Weekly (Tuesday)", wallet.Balance);
|
|
}
|
|
|
|
|
|
// NIC fields used to come from the multi-gym onboarding wizard's OwnerActivation record;
|
|
// that flow no longer exists for a single already-real club (see PayoutKycReviewService's
|
|
// ToDto for the same note) -- always null here now.
|
|
private static ClientPayoutKycDto ToDto(
|
|
ClientPayoutKyc kyc,
|
|
Client client)
|
|
=> new(
|
|
kyc.Id,
|
|
kyc.FKClientId,
|
|
client.ClientName,
|
|
kyc.SelectedGatewayId,
|
|
kyc.SelectedGatewayName,
|
|
kyc.KycStatus.ToString(),
|
|
kyc.SubmittedAt,
|
|
kyc.ReviewedAt,
|
|
kyc.RejectionReason,
|
|
null,
|
|
null,
|
|
null);
|
|
}
|
|
}
|