using Stripe; using Stripe.Checkout; using QRRapidoApp.Models; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System; namespace QRRapidoApp.Services { public class StripeService { private readonly IConfiguration _config; private readonly IUserService _userService; private readonly ILogger _logger; public StripeService(IConfiguration config, IUserService userService, ILogger logger) { _config = config; _userService = userService; _logger = logger; StripeConfiguration.ApiKey = _config["Stripe:SecretKey"]; } public async Task CreateCheckoutSessionAsync(string userId, string priceId, string lang = "pt-BR") { var user = await _userService.GetUserAsync(userId); if (user == null) { throw new Exception("User not found"); } var customerId = user.StripeCustomerId; if (string.IsNullOrEmpty(customerId)) { var customerOptions = new CustomerCreateOptions { Email = user.Email, Name = user.Name, Metadata = new Dictionary { { "app_user_id", user.Id } } }; var customerService = new CustomerService(); var customer = await customerService.CreateAsync(customerOptions); customerId = customer.Id; await _userService.UpdateUserStripeCustomerIdAsync(userId, customerId); } var options = new SessionCreateOptions { PaymentMethodTypes = new List { "card" }, Mode = "subscription", LineItems = new List { new SessionLineItemOptions { Price = priceId, Quantity = 1 } }, Customer = customerId, ClientReferenceId = userId, SuccessUrl = $"{_config["App:BaseUrl"]}/Pagamento/Sucesso", CancelUrl = $"{_config["App:BaseUrl"]}/{lang}/Pagamento/SelecaoPlano", AllowPromotionCodes = true, Metadata = new Dictionary { { "user_id", userId } } }; var service = new SessionService(); var session = await service.CreateAsync(options); _logger.LogInformation($"Created Stripe checkout session {session.Id} for user {userId}"); return session.Url; } public async Task HandleWebhookAsync(string json, string signature) { var webhookSecret = _config["Stripe:WebhookSecret"]; var stripeEvent = EventUtility.ConstructEvent(json, signature, webhookSecret); _logger.LogInformation($"Processing Stripe webhook: {stripeEvent.Type}"); switch (stripeEvent.Type) { case "checkout.session.completed": var session = stripeEvent.Data.Object as Session; if (session?.SubscriptionId != null) { var subscriptionService = new SubscriptionService(); var subscription = await subscriptionService.GetAsync(session.SubscriptionId); await ProcessSubscriptionActivation(session.ClientReferenceId, subscription); } break; case "invoice.finalized": var invoice = stripeEvent.Data.Object as 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; } if (subscriptionId != null) { var subscriptionService = new SubscriptionService(); var subscription = await subscriptionService.GetAsync(subscriptionId); var user = await _userService.GetUserByStripeCustomerIdAsync(subscription.CustomerId); if (user != null) { await ProcessSubscriptionActivation(user.Id, subscription); } } break; case "customer.subscription.deleted": var deletedSubscription = stripeEvent.Data.Object as Subscription; if (deletedSubscription != null) { await _userService.DeactivatePremiumStatus(deletedSubscription.Id); } break; default: _logger.LogWarning($"Unhandled Stripe webhook event type: {stripeEvent.Type}"); break; } } private async Task ProcessSubscriptionActivation(string userId, Subscription subscription) { var service = new SubscriptionItemService(); var subItem = service.Get(subscription.Items.Data[0].Id); if (string.IsNullOrEmpty(userId) || subscription == null) { _logger.LogWarning("Could not process subscription activation due to missing userId or subscription data."); return; } var user = await _userService.GetUserAsync(userId); if (user == null) { _logger.LogWarning($"User not found for premium activation: {userId}"); return; } if (string.IsNullOrEmpty(user.StripeCustomerId)) { await _userService.UpdateUserStripeCustomerIdAsync(user.Id, subscription.CustomerId); } await _userService.ActivatePremiumStatus(userId, subscription.Id, subItem.CurrentPeriodEnd); _logger.LogInformation($"Successfully processed premium activation/renewal for user {userId}."); } public async Task GetSubscriptionStatusAsync(string? subscriptionId) { if (string.IsNullOrEmpty(subscriptionId)) return "None"; try { var service = new SubscriptionService(); var subscription = await service.GetAsync(subscriptionId); return subscription.Status; } catch (Exception ex) { _logger.LogError(ex, $"Error getting subscription status for {subscriptionId}"); return "Unknown"; } } public async Task CancelSubscriptionAsync(string subscriptionId) { try { var service = new SubscriptionService(); await service.CancelAsync(subscriptionId, new SubscriptionCancelOptions()); _logger.LogInformation($"Canceled subscription {subscriptionId} via API."); return true; } catch (Exception ex) { _logger.LogError(ex, $"Error canceling subscription {subscriptionId}"); return false; } } /// /// Verifica se a assinatura está dentro do período de 7 dias para reembolso (CDC) /// public bool IsEligibleForRefund(DateTime? subscriptionStartedAt) { if (!subscriptionStartedAt.HasValue) { return false; } var daysSinceSubscription = (DateTime.UtcNow - subscriptionStartedAt.Value).TotalDays; return daysSinceSubscription <= 7; } /// /// Cancela assinatura E processa reembolso total (CDC - 7 dias) /// public async Task<(bool success, string message)> CancelAndRefundSubscriptionAsync(string userId) { try { var user = await _userService.GetUserAsync(userId); if (user == null) { return (false, "Usuário não encontrado"); } if (string.IsNullOrEmpty(user.StripeSubscriptionId)) { return (false, "Nenhuma assinatura ativa encontrada"); } // Verifica elegibilidade para reembolso if (!IsEligibleForRefund(user.SubscriptionStartedAt)) { var daysSince = user.SubscriptionStartedAt.HasValue ? (DateTime.UtcNow - user.SubscriptionStartedAt.Value).TotalDays : 0; return (false, $"Período de reembolso de 7 dias expirado (assinatura criada há {Math.Round(daysSince, 1)} dias). Você ainda pode cancelar a renovação."); } // Busca a assinatura no Stripe var subscriptionService = new SubscriptionService(); var subscription = await subscriptionService.GetAsync(user.StripeSubscriptionId); if (subscription == null) { return (false, "Assinatura não encontrada no Stripe"); } // Cancela a assinatura primeiro await subscriptionService.CancelAsync(subscription.Id, new SubscriptionCancelOptions()); // Busca o último pagamento (invoice) desta assinatura para reembolsar var invoiceService = new InvoiceService(); var invoiceListOptions = new InvoiceListOptions { Subscription = subscription.Id, Limit = 1, Status = "paid" }; var invoices = await invoiceService.ListAsync(invoiceListOptions); var latestInvoice = invoices.Data.FirstOrDefault(); if (latestInvoice == null || latestInvoice.AmountPaid <= 0) { // Mesmo sem invoice, cancela e desativa await _userService.DeactivatePremiumStatus(subscription.Id); return (true, "Assinatura cancelada com sucesso. Nenhum pagamento para reembolsar foi encontrado."); } // Processa o reembolso - Stripe reembolsa automaticamente o último pagamento var refundService = new RefundService(); var refundOptions = new RefundCreateOptions { Amount = latestInvoice.AmountPaid, // Reembolso total Reason = RefundReasons.RequestedByCustomer, Metadata = new Dictionary { { "user_id", userId }, { "subscription_id", subscription.Id }, { "invoice_id", latestInvoice.Id }, { "refund_reason", "CDC 7 dias - Direito de arrependimento" } } }; // Stripe automaticamente encontra o charge/payment_intent correto através do subscription_id no metadata // Alternativamente, podemos buscar o último charge da subscription try { // Tenta reembolsar usando a subscription (Stripe encontra o charge automaticamente) var chargeService = new ChargeService(); var chargeOptions = new ChargeListOptions { Limit = 1, Customer = subscription.CustomerId }; var charges = await chargeService.ListAsync(chargeOptions); var lastCharge = charges.Data.FirstOrDefault(); if (lastCharge != null) { refundOptions.Charge = lastCharge.Id; var refund = await refundService.CreateAsync(refundOptions); if (refund.Status == "succeeded" || refund.Status == "pending") { // Desativa o premium imediatamente no caso de reembolso await _userService.DeactivatePremiumStatus(subscription.Id); _logger.LogInformation($"Successfully refunded and canceled subscription {subscription.Id} for user {userId}. Refund ID: {refund.Id}"); return (true, $"Reembolso processado com sucesso! Você receberá R$ {(latestInvoice.AmountPaid / 100.0):F2} de volta em 5-10 dias úteis."); } else { _logger.LogWarning($"Refund failed with status {refund.Status} for subscription {subscription.Id}"); await _userService.DeactivatePremiumStatus(subscription.Id); return (false, "Falha ao processar reembolso, mas assinatura foi cancelada. Entre em contato com o suporte."); } } else { await _userService.DeactivatePremiumStatus(subscription.Id); return (false, "Assinatura cancelada, mas nenhuma cobrança encontrada para reembolsar. Entre em contato com o suporte."); } } catch (StripeException refundEx) { _logger.LogError(refundEx, $"Error creating refund for subscription {subscription.Id}"); await _userService.DeactivatePremiumStatus(subscription.Id); return (false, $"Assinatura cancelada, mas erro ao processar reembolso: {refundEx.Message}. Entre em contato com o suporte."); } } catch (StripeException ex) { _logger.LogError(ex, $"Stripe error during refund for user {userId}: {ex.Message}"); return (false, $"Erro ao processar reembolso: {ex.StripeError?.Message ?? ex.Message}"); } catch (Exception ex) { _logger.LogError(ex, $"Error processing refund for user {userId}"); return (false, "Erro inesperado ao processar reembolso. Tente novamente mais tarde."); } } } }