287 lines
11 KiB
C#
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");
|
|
}
|
|
}
|
|
}
|
|
} |