using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NexusReader.Application.Abstractions.Services; using NexusReader.Domain.Entities; using NexusReader.Infrastructure.Configuration; using NexusReader.Data.Persistence; using FluentResults; namespace NexusReader.Infrastructure.Services; public class BillingService : IBillingService { private readonly IDbContextFactory _dbContextFactory; private readonly UserManager _userManager; private readonly StripeSettings _stripeSettings; private readonly ILogger _logger; public BillingService( IDbContextFactory dbContextFactory, UserManager userManager, IOptions stripeSettings, ILogger logger) { _dbContextFactory = dbContextFactory; _userManager = userManager; _stripeSettings = stripeSettings.Value; _logger = logger; } public async Task HandleSubscriptionUpdatedAsync(string customerEmail, string stripeProductId) { try { var user = await _userManager.FindByEmailAsync(customerEmail); if (user == null) { _logger.LogWarning("Attempted to update subscription for non-existent user: {Email}", customerEmail); return Result.Fail($"User {customerEmail} not found."); } string targetPlanName = SubscriptionPlan.FreeName; int tokenLimit = 1000; if (stripeProductId == _stripeSettings.ProProductId) { targetPlanName = SubscriptionPlan.ProName; tokenLimit = 50000; } else if (stripeProductId == _stripeSettings.BasicProductId) { targetPlanName = SubscriptionPlan.BasicName; tokenLimit = 10000; } else if (!string.IsNullOrEmpty(stripeProductId) && stripeProductId != _stripeSettings.FreeProductId) { _logger.LogWarning("Unrecognized Stripe Product ID: {ProductId} for user {Email}. Falling back to Free tier.", stripeProductId, customerEmail); } using var dbContext = await _dbContextFactory.CreateDbContextAsync(); var plan = await dbContext.SubscriptionPlans.FirstOrDefaultAsync(p => p.PlanName == targetPlanName); if (plan != null) { user.SubscriptionPlanId = plan.Id; user.AITokenLimit = tokenLimit; } var result = await _userManager.UpdateAsync(user); if (!result.Succeeded) { _logger.LogError("Failed to update user {Email} after subscription change: {Errors}", customerEmail, string.Join(", ", result.Errors.Select(e => e.Description))); return Result.Fail(result.Errors.Select(e => e.Description).FirstOrDefault() ?? "Failed to update user profile."); } return Result.Ok(); } catch (Exception ex) { _logger.LogError(ex, "Unexpected error during subscription update for {Email}", customerEmail); return Result.Fail(new Error("Unexpected error during subscription update.").CausedBy(ex)); } } public async Task HandleSubscriptionDeletedAsync(string customerEmail) { try { var user = await _userManager.FindByEmailAsync(customerEmail); if (user == null) { _logger.LogWarning("Attempted to delete subscription for non-existent user: {Email}", customerEmail); return Result.Fail($"User {customerEmail} not found."); } using var dbContext = await _dbContextFactory.CreateDbContextAsync(); var freePlan = await dbContext.SubscriptionPlans.FirstOrDefaultAsync(p => p.PlanName == SubscriptionPlan.FreeName); if (freePlan != null) { user.SubscriptionPlanId = freePlan.Id; user.AITokenLimit = freePlan.AITokenLimit; } var result = await _userManager.UpdateAsync(user); if (!result.Succeeded) { _logger.LogError("Failed to reset user {Email} to Free tier after subscription deletion: {Errors}", customerEmail, string.Join(", ", result.Errors.Select(e => e.Description))); return Result.Fail(result.Errors.Select(e => e.Description).FirstOrDefault() ?? "Failed to reset user to free tier."); } return Result.Ok(); } catch (Exception ex) { _logger.LogError(ex, "Unexpected error during subscription deletion for {Email}", customerEmail); return Result.Fail(new Error("Unexpected error during subscription deletion.").CausedBy(ex)); } } }