using Microsoft.AspNetCore.Mvc; using Stripe; using BCards.Web.Services; using BCards.Web.Repositories; using BCards.Web.Configuration; using Microsoft.Extensions.Options; namespace BCards.Web.Controllers; [ApiController] [Route("api/stripe")] public class StripeWebhookController : ControllerBase { private readonly ILogger _logger; private readonly ISubscriptionRepository _subscriptionRepository; private readonly IUserPageService _userPageService; private readonly string _webhookSecret; public StripeWebhookController( ILogger logger, ISubscriptionRepository subscriptionRepository, IUserPageService userPageService, IOptions stripeSettings) { _logger = logger; _subscriptionRepository = subscriptionRepository; _userPageService = userPageService; _webhookSecret = stripeSettings.Value.WebhookSecret ?? ""; } [HttpPost("webhook")] public async Task HandleWebhook() { try { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); _logger.LogInformation($"Recebido:{json}"); if (string.IsNullOrEmpty(_webhookSecret)) { _logger.LogWarning("Webhook secret not configured"); return BadRequest("Webhook secret not configured"); } _logger.LogWarning($"Recebido:{json}"); var stripeSignature = Request.Headers["Stripe-Signature"].FirstOrDefault(); if (string.IsNullOrEmpty(stripeSignature)) { _logger.LogWarning("Missing Stripe signature"); return BadRequest("Missing Stripe signature"); } var stripeEvent = EventUtility.ConstructEvent( json, stripeSignature, _webhookSecret, throwOnApiVersionMismatch: false ); _logger.LogInformation($"Processing Stripe webhook: {stripeEvent.Type}"); switch (stripeEvent.Type) { case "invoice.payment_succeeded": await HandlePaymentSucceeded(stripeEvent); break; case "invoice.payment_failed": await HandlePaymentFailed(stripeEvent); break; case "customer.subscription.deleted": await HandleSubscriptionDeleted(stripeEvent); break; case "customer.subscription.updated": await HandleSubscriptionUpdated(stripeEvent); break; default: _logger.LogInformation($"Unhandled webhook event type: {stripeEvent.Type}"); break; } return Ok(); } catch (StripeException ex) { _logger.LogError(ex, "Stripe webhook error"); return BadRequest($"Stripe error: {ex.Message}"); } catch (Exception ex) { _logger.LogError(ex, "Webhook processing error"); return StatusCode(500, "Internal server error"); } } private async Task HandlePaymentSucceeded(Event stripeEvent) { if (stripeEvent.Data.Object is Invoice invoice) { _logger.LogInformation($"Payment succeeded for customer: {invoice.CustomerId}"); var subscriptionId = GetSubscriptionId(stripeEvent); var subscription = await _subscriptionRepository.GetByStripeSubscriptionIdAsync(subscriptionId); if (subscription != null) { subscription.Status = "active"; subscription.UpdatedAt = DateTime.UtcNow; await _subscriptionRepository.UpdateAsync(subscription); // Reactivate user pages var userPages = await _userPageService.GetUserPagesAsync(subscription.UserId); foreach (var page in userPages.Where(p => p.Status == ViewModels.PageStatus.PendingPayment)) { page.Status = ViewModels.PageStatus.Active; page.UpdatedAt = DateTime.UtcNow; await _userPageService.UpdatePageAsync(page); } _logger.LogInformation($"Reactivated {userPages.Count} pages for user {subscription.UserId}"); } } } private async Task HandlePaymentFailed(Event stripeEvent) { if (stripeEvent.Data.Object is Invoice invoice) { _logger.LogInformation($"Payment failed for customer: {invoice.CustomerId}"); var subscriptionId = GetSubscriptionId(stripeEvent); var subscription = await _subscriptionRepository.GetByStripeSubscriptionIdAsync(subscriptionId); if (subscription != null) { subscription.Status = "past_due"; subscription.UpdatedAt = DateTime.UtcNow; await _subscriptionRepository.UpdateAsync(subscription); // Set pages to pending payment var userPages = await _userPageService.GetUserPagesAsync(subscription.UserId); foreach (var page in userPages.Where(p => p.Status == ViewModels.PageStatus.Active)) { page.Status = ViewModels.PageStatus.PendingPayment; page.UpdatedAt = DateTime.UtcNow; await _userPageService.UpdatePageAsync(page); } _logger.LogInformation($"Set {userPages.Count} pages to pending payment for user {subscription.UserId}"); } } } private async Task HandleSubscriptionDeleted(Event stripeEvent) { if (stripeEvent.Data.Object is Subscription stripeSubscription) { _logger.LogInformation($"Subscription cancelled: {stripeSubscription.Id}"); var subscription = await _subscriptionRepository.GetByStripeSubscriptionIdAsync(stripeSubscription.Id); if (subscription != null) { subscription.Status = "cancelled"; subscription.UpdatedAt = DateTime.UtcNow; await _subscriptionRepository.UpdateAsync(subscription); // Downgrade to trial or deactivate pages var userPages = await _userPageService.GetUserPagesAsync(subscription.UserId); foreach (var page in userPages.Where(p => p.Status == ViewModels.PageStatus.Active)) { page.Status = ViewModels.PageStatus.Expired; page.UpdatedAt = DateTime.UtcNow; await _userPageService.UpdatePageAsync(page); } _logger.LogInformation($"Deactivated {userPages.Count} pages for cancelled subscription {subscription.UserId}"); } } } private async Task HandleSubscriptionUpdated(Event stripeEvent) { if (stripeEvent.Data.Object is Subscription stripeSubscription) { _logger.LogInformation($"Subscription updated: {stripeSubscription.Id}"); var subscription = await _subscriptionRepository.GetByStripeSubscriptionIdAsync(stripeSubscription.Id); if (subscription != null) { var service = new SubscriptionItemService(); var subItem = service.Get(stripeSubscription.Items.Data[0].Id); subscription.Status = stripeSubscription.Status; subscription.CurrentPeriodStart = subItem.CurrentPeriodStart; subscription.CurrentPeriodEnd = subItem.CurrentPeriodEnd; subscription.CancelAtPeriodEnd = stripeSubscription.CancelAtPeriodEnd; subscription.UpdatedAt = DateTime.UtcNow; // Update plan type based on Stripe price ID var priceId = stripeSubscription.Items.Data.FirstOrDefault()?.Price.Id; if (!string.IsNullOrEmpty(priceId)) { subscription.PlanType = MapPriceIdToPlanType(priceId); } await _subscriptionRepository.UpdateAsync(subscription); _logger.LogInformation($"Updated subscription for user {subscription.UserId}"); } } } private string MapPriceIdToPlanType(string priceId) { // Map Stripe price IDs to plan types // This would be configured based on your actual Stripe price IDs return priceId switch { "price_1RjUskBMIadsOxJVgLwlVo1y" => "Basic", "price_1RjUv9BMIadsOxJVORqlM4E9" => "Professional", "price_1RjUw0BMIadsOxJVmdouNV1g" => "Premium", "price_basic_yearly_placeholder" => "BasicYearly", "price_professional_yearly_placeholder" => "ProfessionalYearly", "price_premium_yearly_placeholder" => "PremiumYearly", var id when id.Contains("basic") && id.Contains("yearly") => "BasicYearly", var id when id.Contains("professional") && id.Contains("yearly") => "ProfessionalYearly", var id when id.Contains("premium") && id.Contains("yearly") => "PremiumYearly", var id when id.Contains("basic") => "Basic", var id when id.Contains("professional") => "Professional", var id when id.Contains("premium") => "Premium", _ => "Trial" }; } private string GetSubscriptionId(Event stripeEvent) { if (stripeEvent.Data.Object is Invoice invoice) { var subscriptionLineItem = invoice.Lines?.Data .FirstOrDefault(line => !string.IsNullOrEmpty(line.SubscriptionId) || line.Subscription != null ); string subscriptionId = null; if (subscriptionLineItem != null) { // Tenta obter o ID da assinatura de duas formas diferentes subscriptionId = subscriptionLineItem.SubscriptionId ?? subscriptionLineItem.Subscription?.Id; } return subscriptionId; } else if (stripeEvent.Data.Object is Subscription stripeSubscription) { return stripeSubscription.Id; } return null; } }