BCards/src/BCards.Web/Controllers/PaymentController.cs
Ricardo Carneiro ed9fe4902b
All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 3s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 15m29s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 2m25s
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
fix: casas decimais
2025-09-09 22:10:18 -03:00

279 lines
10 KiB
C#

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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<PlanType>(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<PlanType>(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<IActionResult> 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<IActionResult> 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<IActionResult> 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<AvailablePlanViewModel> GetAvailablePlans(string currentPlan)
{
var plansConfig = _configuration.GetSection("Plans");
var plans = new List<AvailablePlanViewModel>();
// 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<List<string>>() ?? new List<string>(),
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();
}
}