using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using QRRapidoApp.Services; using QRRapidoApp.Models; using System.Security.Claims; using System.Threading.Tasks; using QRRapidoApp.Models.ViewModels; using System.Linq; using MongoDB.Driver; using QRRapidoApp.Data; using System.Text; using Stripe.Checkout; namespace QRRapidoApp.Controllers { [Authorize] public class PagamentoController : Controller { private readonly IUserService _userService; private readonly ILogger _logger; private readonly MongoDbContext _context; private readonly AdDisplayService _adDisplayService; private readonly StripeService _stripeService; // Injected StripeService private readonly string _pixKey = "chave-pix-padrao@qrrapido.site"; private readonly string _merchantName = "QR Rapido"; private readonly string _merchantCity = "SAO PAULO"; public PagamentoController( IUserService userService, ILogger logger, MongoDbContext context, AdDisplayService adDisplayService, IConfiguration config, StripeService stripeService) { _userService = userService; _logger = logger; _context = context; _adDisplayService = adDisplayService; _stripeService = stripeService; var configPixKey = config["Payment:PixKey"]; if (!string.IsNullOrEmpty(configPixKey)) { _pixKey = configPixKey; } } [HttpGet] public async Task SelecaoPlano() { _adDisplayService.SetViewBagAds(ViewBag); // Definição dos pacotes com PREÇOS DIFERENCIADOS var packages = GetPackages(); return View(packages); } [HttpPost("api/Pagamento/CreatePixOrder")] public async Task CreatePixOrder([FromBody] CreateOrderRequest request) { var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; var userEmail = User.FindFirst(ClaimTypes.Email)?.Value; if (string.IsNullOrEmpty(userId)) return Unauthorized(); var package = GetPackage(request.PackageId); if (package == null) return BadRequest("Pacote inválido"); try { // Create Order (PIX Price) var order = new Order { UserId = userId, UserEmail = userEmail ?? "unknown", Amount = package.PricePix, CreditsAmount = package.Credits, Status = "Pending", CreatedAt = DateTime.UtcNow }; await _context.Orders.InsertOneAsync(order); var shortId = order.Id.Substring(order.Id.Length - 8).ToUpper(); var txId = $"PED{shortId}"; var update = Builders.Update.Set(o => o.PixCode, txId); await _context.Orders.UpdateOneAsync(o => o.Id == order.Id, update); var pixPayload = PixPayloadGenerator.GeneratePayload( _pixKey, package.PricePix, _merchantName, _merchantCity, txId ); return Ok(new { success = true, pixCode = pixPayload, orderId = txId, amount = package.PricePix, credits = package.Credits }); } catch (Exception ex) { _logger.LogError(ex, "Erro ao gerar pedido PIX"); return StatusCode(500, new { success = false, error = "Erro interno" }); } } [HttpPost("api/Pagamento/CreateStripeSession")] public async Task CreateStripeSession([FromBody] CreateOrderRequest request) { var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userId)) return Unauthorized(); var package = GetPackage(request.PackageId); if (package == null) return BadRequest("Pacote inválido"); try { // Create Stripe Checkout Session (One-Time Payment) // We create an ad-hoc price on the fly using line_items var options = new SessionCreateOptions { PaymentMethodTypes = new List { "card" }, Mode = "payment", // One-time payment LineItems = new List { new SessionLineItemOptions { PriceData = new SessionLineItemPriceDataOptions { Currency = "brl", UnitAmount = (long)(package.PriceCard * 100), // Centavos ProductData = new SessionLineItemPriceDataProductDataOptions { Name = $"{package.Credits} Créditos QR Rapido", Description = package.Description } }, Quantity = 1, }, }, Metadata = new Dictionary { { "user_id", userId }, { "credits_amount", package.Credits.ToString() }, { "package_id", package.Id } }, SuccessUrl = $"{Request.Scheme}://{Request.Host}/Pagamento/Sucesso", CancelUrl = $"{Request.Scheme}://{Request.Host}/Pagamento/SelecaoPlano", }; var service = new SessionService(); var session = await service.CreateAsync(options); return Ok(new { success = true, url = session.Url }); } catch (Exception ex) { _logger.LogError(ex, "Stripe session error"); return StatusCode(500, new { success = false, error = ex.Message }); } } private List GetPackages() { return new List { new CreditPackageViewModel { Id = "starter", Name = "Iniciante", Credits = 10, PricePix = 5.00m, // R$ 0,50/un PriceCard = 6.00m, Description = "Ideal para testes rápidos", Savings = 0 }, new CreditPackageViewModel { Id = "pro", Name = "Profissional", Credits = 50, PricePix = 22.50m, // R$ 0,45/un (Desconto de volume) PriceCard = 27.00m, Description = "Para uso recorrente", Savings = 10, IsPopular = true }, new CreditPackageViewModel { Id = "agency", Name = "Agência", Credits = 100, PricePix = 40.00m, // R$ 0,40/un (Super desconto) PriceCard = 48.00m, Description = "Volume alto com desconto máximo", Savings = 20 } }; } private CreditPackageViewModel? GetPackage(string id) { return GetPackages().FirstOrDefault(p => p.Id == id); } public class CreateOrderRequest { public string PackageId { get; set; } } public class CreditPackageViewModel { public string Id { get; set; } public string Name { get; set; } public int Credits { get; set; } public decimal PricePix { get; set; } // Preço PIX public decimal PriceCard { get; set; } // Preço Cartão (+taxas) public string Description { get; set; } public int Savings { get; set; } public bool IsPopular { get; set; } } // --- Helper Interno para Gerar PIX (CRC16) --- public static class PixPayloadGenerator { public static string GeneratePayload(string pixKey, decimal amount, string merchantName, string merchantCity, string txId) { var sb = new StringBuilder(); sb.Append(FormatField("00", "01")); var merchantInfo = new StringBuilder(); merchantInfo.Append(FormatField("00", "br.gov.bcb.pix")); merchantInfo.Append(FormatField("01", pixKey)); sb.Append(FormatField("26", merchantInfo.ToString())); sb.Append(FormatField("52", "0000")); sb.Append(FormatField("53", "986")); sb.Append(FormatField("54", amount.ToString("F2", System.Globalization.CultureInfo.InvariantCulture))); sb.Append(FormatField("58", "BR")); var name = merchantName.Length > 25 ? merchantName.Substring(0, 25) : merchantName; sb.Append(FormatField("59", RemoveAccents(name))); var city = merchantCity.Length > 15 ? merchantCity.Substring(0, 15) : merchantCity; sb.Append(FormatField("60", RemoveAccents(city))); var additionalData = new StringBuilder(); additionalData.Append(FormatField("05", txId)); sb.Append(FormatField("62", additionalData.ToString())); sb.Append("6304"); var payloadWithoutCrc = sb.ToString(); var crc = CalculateCRC16(payloadWithoutCrc); return payloadWithoutCrc + crc; } private static string FormatField(string id, string value) => $"{id}{value.Length:D2}{value}"; private static string RemoveAccents(string text) { return text.ToUpper() .Replace("Ã", "A").Replace("Á", "A").Replace("Â", "A") .Replace("É", "E").Replace("Ê", "E") .Replace("Í", "I") .Replace("Ó", "O").Replace("Ô", "O").Replace("Õ", "O") .Replace("Ú", "U") .Replace("Ç", "C"); } private static string CalculateCRC16(string data) { ushort crc = 0xFFFF; byte[] bytes = Encoding.ASCII.GetBytes(data); foreach (byte b in bytes) { crc ^= (ushort)(b << 8); for (int i = 0; i < 8; i++) { if ((crc & 0x8000) != 0) crc = (ushort)((crc << 1) ^ 0x1021); else crc <<= 1; } } return crc.ToString("X4"); } } } }