QrRapido/Controllers/PagamentoController.cs
Ricardo Carneiro 16a9720a12
All checks were successful
Deploy QR Rapido / test (push) Successful in 59s
Deploy QR Rapido / build-and-push (push) Successful in 9m57s
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Successful in 2m11s
feat: qrcode por creditos.
2026-01-26 20:13:45 -03:00

287 lines
11 KiB
C#

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<PagamentoController> _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<PagamentoController> 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<IActionResult> 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<IActionResult> 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<Order>.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<IActionResult> 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<string> { "card" },
Mode = "payment", // One-time payment
LineItems = new List<SessionLineItemOptions>
{
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<string, string>
{
{ "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<CreditPackageViewModel> GetPackages()
{
return new List<CreditPackageViewModel>
{
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");
}
}
}
}