using BCards.Web.Models; using BCards.Web.Repositories; using MongoDB.Driver; namespace BCards.Web.Services; public class TrialExpirationService : BackgroundService { private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private readonly TimeSpan _checkInterval = TimeSpan.FromHours(1); // Check every hour public TrialExpirationService( IServiceProvider serviceProvider, ILogger logger) { _serviceProvider = serviceProvider; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("TrialExpirationService started"); while (!stoppingToken.IsCancellationRequested) { try { await ProcessTrialExpirationsAsync(); // Verificar cancelamento antes de fazer delay if (stoppingToken.IsCancellationRequested) break; await Task.Delay(_checkInterval, stoppingToken); } catch (OperationCanceledException) { // Cancelamento normal - não é erro _logger.LogInformation("TrialExpirationService is being cancelled"); break; } catch (Exception ex) { _logger.LogError(ex, "Error processing trial expirations"); // Verificar cancelamento antes de fazer delay de erro if (stoppingToken.IsCancellationRequested) break; try { await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); // Wait 5 minutes on error } catch (OperationCanceledException) { // Cancelamento durante delay de erro - também é normal _logger.LogInformation("TrialExpirationService cancelled during error delay"); break; } } } _logger.LogInformation("TrialExpirationService stopped"); } private async Task ProcessTrialExpirationsAsync() { try { using var scope = _serviceProvider.CreateScope(); var subscriptionRepository = scope.ServiceProvider.GetRequiredService(); var userPageRepository = scope.ServiceProvider.GetRequiredService(); var userRepository = scope.ServiceProvider.GetRequiredService(); _logger.LogInformation("Checking for expired trials..."); // Get all active trial subscriptions var trialSubscriptions = await subscriptionRepository.GetTrialSubscriptionsAsync(); var now = DateTime.UtcNow; _logger.LogInformation($"Found {trialSubscriptions.Count} trial subscriptions to process"); foreach (var subscription in trialSubscriptions) { try { var user = await userRepository.GetByIdAsync(subscription.UserId); if (user == null) { _logger.LogWarning($"User not found for subscription {subscription.Id}"); continue; } var daysUntilExpiration = (subscription.CurrentPeriodEnd - now).TotalDays; if (daysUntilExpiration <= 0) { // Trial expired - deactivate page _logger.LogInformation($"Trial expired for user {user.Email}"); await HandleTrialExpiredAsync(user, subscription, userPageRepository); } else if (daysUntilExpiration <= 2 && !user.NotifiedOfExpiration) { // Trial expiring soon - send notification _logger.LogInformation($"Trial expiring in {daysUntilExpiration:F1} days for user {user.Email}"); await SendExpirationWarningAsync(user, subscription, daysUntilExpiration); // Mark as notified user.NotifiedOfExpiration = true; await userRepository.UpdateAsync(user); } } catch (Exception ex) { _logger.LogError(ex, $"Error processing trial for subscription {subscription.Id}"); } } _logger.LogInformation("Finished checking trial expirations"); } catch (Exception ex) { _logger.LogError(ex, "Critical error in ProcessTrialExpirationsAsync"); throw; // Re-throw para ser tratado pelo ExecuteAsync } } private async Task HandleTrialExpiredAsync( User user, Subscription subscription, IUserPageRepository userPageRepository) { // Deactivate user page var userPage = await userPageRepository.GetByUserIdAsync(user.Id); if (userPage != null) { userPage.IsActive = false; userPage.UpdatedAt = DateTime.UtcNow; await userPageRepository.UpdateAsync(userPage); } // Update subscription status subscription.Status = "expired"; subscription.UpdatedAt = DateTime.UtcNow; using var scope = _serviceProvider.CreateScope(); var subscriptionRepository = scope.ServiceProvider.GetRequiredService(); await subscriptionRepository.UpdateAsync(subscription); // Send expiration email await SendTrialExpiredEmailAsync(user); _logger.LogInformation($"Deactivated trial page for user {user.Email}"); } private async Task SendExpirationWarningAsync( User user, Subscription subscription, double daysRemaining) { // TODO: Implement email service // For now, just log _logger.LogInformation($"Should send expiration warning to {user.Email} - {daysRemaining:F1} days remaining"); // Example email content: var subject = "Seu trial do BCards expira em breve!"; var message = $@" Olá {user.Name}, Seu trial gratuito do BCards expira em {Math.Ceiling(daysRemaining)} dia(s). Para continuar usando sua página de links, escolha um de nossos planos: • Básico - R$ 9,90/mês • Profissional - R$ 24,90/mês • Premium - R$ 29,90/mês Acesse: {GetUpgradeUrl()} Equipe BCards "; // TODO: Send actual email when email service is implemented await Task.CompletedTask; } private async Task SendTrialExpiredEmailAsync(User user) { // TODO: Implement email service _logger.LogInformation($"Should send trial expired email to {user.Email}"); var subject = "Seu trial do BCards expirou"; var message = $@" Olá {user.Name}, Seu trial gratuito do BCards expirou e sua página foi temporariamente desativada. Para reativar sua página, escolha um de nossos planos: • Básico - R$ 9,90/mês - 5 links, analytics básicos • Profissional - R$ 24,90/mês - 15 links, todos os temas, analytics avançados • Premium - R$ 29,90/mês - Links ilimitados, temas premium, analytics completos Seus dados estão seguros e serão restaurados assim que você escolher um plano. Acesse: {GetUpgradeUrl()} Equipe BCards "; // TODO: Send actual email when email service is implemented await Task.CompletedTask; } private string GetUpgradeUrl() { // TODO: Get from configuration return "https://bcards.com.br/pricing"; } }