using BCards.Web.Models; using BCards.Web.Repositories; using BCards.Web.Services; using BCards.Web.ViewModels; using BCards.Web.Utils; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using System.Globalization; namespace BCards.Web.Controllers; [Authorize] public class PaymentController : Controller { private readonly IPaymentService _paymentService; private readonly IAuthService _authService; private readonly IUserRepository _userService; private readonly ISubscriptionRepository _subscriptionRepository; private readonly IConfiguration _configuration; public PaymentController(IPaymentService paymentService, IAuthService authService, IUserRepository userService, ISubscriptionRepository subscriptionRepository, IConfiguration configuration) { _paymentService = paymentService; _authService = authService; _userService = userService; _subscriptionRepository = subscriptionRepository; _configuration = configuration; } [HttpPost] public async Task CreateCheckoutSession(string planType) { var user = await _authService.GetCurrentUserAsync(User); if (user == null) return RedirectToAction("Login", "Auth"); var successUrl = Url.Action("Success", "Payment", null, Request.Scheme); var cancelUrl = Url.Action("Cancel", "Payment", null, Request.Scheme); TempData[$"PlanType|{user.Id}"] = planType; try { var checkoutUrl = await _paymentService.CreateCheckoutSessionAsync( user.Id, planType, successUrl!, cancelUrl!); return Redirect(checkoutUrl); } catch (Exception ex) { TempData["Error"] = $"Erro ao processar pagamento: {ex.Message}"; return RedirectToAction("Pricing", "Home"); } } public async Task Success() { try { var user = await _authService.GetCurrentUserAsync(User); var planType = TempData[$"PlanType|{user.Id}"].ToString(); TempData["Success"] = $"Assinatura {planType} ativada com sucesso!"; return RedirectToAction("Dashboard", "Admin"); } catch (Exception ex) { TempData["Error"] = $"Erro ao processar pagamento: {ex.Message}"; return RedirectToAction("Dashboard", "Admin"); } } public IActionResult Cancel() { TempData["Info"] = "Pagamento cancelado. Você pode tentar novamente quando quiser."; return RedirectToAction("Pricing", "Home"); } [HttpPost] [Route("webhook/stripe")] [AllowAnonymous] public async Task StripeWebhook() { var signature = Request.Headers["Stripe-Signature"].FirstOrDefault(); if (string.IsNullOrEmpty(signature)) return BadRequest(); string requestBody; using (var reader = new StreamReader(Request.Body)) { requestBody = await reader.ReadToEndAsync(); } try { await _paymentService.HandleWebhookAsync(requestBody, signature); return Ok(); } catch (Exception ex) { return BadRequest($"Webhook error: {ex.Message}"); } } public async Task ManageSubscription() { var user = await _authService.GetCurrentUserAsync(User); if (user == null) return RedirectToAction("Login", "Auth"); try { // Parse do plano atual (mesmo que o Dashboard) var userPlanType = Enum.TryParse(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial; var currentPlanString = userPlanType.ToString().ToLower(); var subscription = await _subscriptionRepository.GetByUserIdAsync(user.Id); var viewModel = new ManageSubscriptionViewModel { User = user, StripeSubscription = await _paymentService.GetSubscriptionDetailsAsync(user.Id), PaymentHistory = await _paymentService.GetPaymentHistoryAsync(user.Id), AvailablePlans = GetAvailablePlans(currentPlanString), CurrentPeriodEnd = (DateTime?) subscription.CurrentPeriodEnd }; // Pegar assinatura local se existir if (!string.IsNullOrEmpty(user.StripeCustomerId)) { // Aqui você poderia buscar a subscription local se necessário // viewModel.LocalSubscription = await _subscriptionRepository.GetByUserIdAsync(user.Id); } return View(viewModel); } catch (Exception ex) { // Parse do plano atual também no catch var userPlanType = Enum.TryParse(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial; var currentPlanString = userPlanType.ToString().ToLower(); var errorViewModel = new ManageSubscriptionViewModel { User = user, ErrorMessage = $"Erro ao carregar dados da assinatura: {ex.Message}", AvailablePlans = GetAvailablePlans(currentPlanString) }; return View(errorViewModel); } } [HttpPost] public async Task CancelSubscription(string subscriptionId) { try { await _paymentService.CancelSubscriptionAsync(subscriptionId); TempData["Success"] = "Sua assinatura será cancelada no final do período atual."; } catch (Exception ex) { TempData["Error"] = $"Erro ao cancelar assinatura: {ex.Message}"; } return RedirectToAction("ManageSubscription"); } [HttpPost] public async Task ChangePlan(string newPlanType) { var user = await _authService.GetCurrentUserAsync(User); if (user == null) return RedirectToAction("Login", "Auth"); try { // Para mudanças de plano, vamos usar o Stripe Checkout var returnUrl = Url.Action("ManageSubscription", "Payment", null, Request.Scheme); var cancelUrl = Url.Action("ManageSubscription", "Payment", null, Request.Scheme); var checkoutUrl = await _paymentService.CreateCheckoutSessionAsync( user.Id, newPlanType, returnUrl!, cancelUrl!); return Redirect(checkoutUrl); } catch (Exception ex) { TempData["Error"] = $"Erro ao alterar plano: {ex.Message}"; return RedirectToAction("ManageSubscription"); } } [HttpPost] public async Task OpenStripePortal() { var user = await _authService.GetCurrentUserAsync(User); if (user == null || string.IsNullOrEmpty(user.StripeCustomerId)) { TempData["Error"] = "Erro: dados de assinatura não encontrados."; return RedirectToAction("ManageSubscription"); } try { var returnUrl = Url.Action("ManageSubscription", "Payment", null, Request.Scheme); var portalUrl = await _paymentService.CreatePortalSessionAsync(user.StripeCustomerId, returnUrl!); return Redirect(portalUrl); } catch (Exception ex) { TempData["Error"] = $"Erro ao abrir portal de pagamento: {ex.Message}"; return RedirectToAction("ManageSubscription"); } } private List GetAvailablePlans(string currentPlan) { var plansConfig = _configuration.GetSection("Plans"); var plans = new List(); // Adicionar planos mensais apenas (excluir Trial e planos anuais) var monthlyPlans = new[] { "Basic", "Professional", "Premium", "PremiumAffiliate" }; foreach (var planKey in monthlyPlans) { var planSection = plansConfig.GetSection(planKey); if (planSection.Exists()) { plans.Add(new AvailablePlanViewModel { PlanType = planKey.ToLower(), DisplayName = planSection["Name"] ?? planKey, Price = decimal.Parse(planSection["Price"] ?? "0", new CultureInfo("en-US")), PriceId = planSection["PriceId"] ?? "", MaxLinks = int.Parse(planSection["MaxLinks"] ?? "0"), AllowAnalytics = bool.Parse(planSection["AllowAnalytics"] ?? "false"), AllowCustomDomain = true, // URL personalizada em todos os planos pagos AllowCustomThemes = bool.Parse(planSection["AllowPremiumThemes"] ?? "false"), AllowProductLinks = bool.Parse(planSection["AllowProductLinks"] ?? "false"), Features = planSection.GetSection("Features").Get>() ?? new List(), IsCurrentPlan = currentPlan.Equals(planKey, StringComparison.OrdinalIgnoreCase) }); } } // Marcar upgrades e filtrar downgrades var currentPlanIndex = plans.FindIndex(p => p.IsCurrentPlan); // Se usuário está no Trial (não encontrou plano atual), todos são upgrades if (currentPlanIndex == -1 && (currentPlan == "trial" || currentPlan == "free")) { foreach (var plan in plans) { plan.IsUpgrade = true; } return plans; // Mostrar todos os planos pagos como upgrade } // Para planos pagos, marcar apenas upgrades superiores for (int i = 0; i < plans.Count; i++) { if (i > currentPlanIndex) plans[i].IsUpgrade = true; else if (i < currentPlanIndex) plans[i].IsDowngrade = true; } // Retornar apenas plano atual e upgrades (Stripe não gerencia downgrades automaticamente) return plans.Where(p => p.IsCurrentPlan || p.IsUpgrade).ToList(); } }