All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 2s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 8m1s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 3m48s
BCards Deployment Pipeline / Deploy to Staging (x86 - Local) (push) Has been skipped
BCards Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
BCards Deployment Pipeline / Deployment Summary (push) Successful in 0s
221 lines
8.1 KiB
C#
221 lines
8.1 KiB
C#
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<TrialExpirationService> _logger;
|
|
private readonly TimeSpan _checkInterval = TimeSpan.FromHours(1); // Check every hour
|
|
|
|
public TrialExpirationService(
|
|
IServiceProvider serviceProvider,
|
|
ILogger<TrialExpirationService> 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<ISubscriptionRepository>();
|
|
var userPageRepository = scope.ServiceProvider.GetRequiredService<IUserPageRepository>();
|
|
var userRepository = scope.ServiceProvider.GetRequiredService<IUserRepository>();
|
|
|
|
_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<ISubscriptionRepository>();
|
|
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";
|
|
}
|
|
} |