Init Commit

This commit is contained in:
2026-01-29 13:43:44 -03:00
commit b9aa8478db
126 changed files with 20649 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MotoresArgentinosV2.Core.DTOs;
using MotoresArgentinosV2.Core.Interfaces;
using Microsoft.AspNetCore.RateLimiting;
using System.Security.Cryptography;
using System.Text;
namespace MotoresArgentinosV2.API.Controllers;
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class PaymentsController : ControllerBase
{
private readonly IPaymentService _paymentService;
private readonly IConfiguration _config;
public PaymentsController(IPaymentService paymentService, IConfiguration config)
{
_paymentService = paymentService;
_config = config;
}
[HttpPost("process")]
[EnableRateLimiting("AuthPolicy")]
public async Task<IActionResult> ProcessPayment([FromBody] CreatePaymentRequestDto request)
{
try
{
var userId = int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0");
var result = await _paymentService.ProcessPaymentAsync(request, userId);
if (result.Status == "approved")
{
return Ok(new { status = "approved", id = result.PaymentId });
}
else if (result.Status == "in_process")
{
return Ok(new { status = "in_process", message = "El pago está siendo revisado." });
}
else
{
return BadRequest(new { status = "rejected", detail = result.StatusDetail });
}
}
catch (Exception ex)
{
return StatusCode(500, new { message = ex.Message });
}
}
// --- MÉTODO WEBHOOK ACTUALIZADO CON VALIDACIÓN DE FIRMA ---
[HttpPost("webhook")]
[AllowAnonymous]
public async Task<IActionResult> MercadoPagoWebhook([FromQuery] string? topic, [FromQuery] string? id)
{
// 1. EXTRACCIÓN DE DATOS DE LA PETICIÓN
Request.Headers.TryGetValue("x-request-id", out var xRequestId);
Request.Headers.TryGetValue("x-signature", out var xSignature);
var resourceId = id;
var resourceTopic = topic;
if (string.IsNullOrEmpty(resourceId))
{
try
{
Request.EnableBuffering();
using var reader = new System.IO.StreamReader(Request.Body, leaveOpen: true);
var body = await reader.ReadToEndAsync();
Request.Body.Position = 0;
var json = System.Text.Json.JsonDocument.Parse(body);
if (json.RootElement.TryGetProperty("type", out var typeProp))
{
resourceTopic = typeProp.GetString();
}
if (json.RootElement.TryGetProperty("data", out var dataProp) && dataProp.TryGetProperty("id", out var idProp))
{
resourceId = idProp.GetString();
}
}
catch { /* Ignorar errores de lectura */ }
}
if (string.IsNullOrEmpty(resourceId) || resourceTopic != "payment")
{
return Ok();
}
// 2. VALIDACIÓN DE LA FIRMA
var secret = _config["MercadoPago:WebhookSecret"];
if (!string.IsNullOrEmpty(secret))
{
var signatureParts = xSignature.ToString().Split(',')
.Select(part => part.Trim().Split('='))
.ToDictionary(split => split[0], split => split.Length > 1 ? split[1] : "");
if (!signatureParts.TryGetValue("ts", out var ts) || !signatureParts.TryGetValue("v1", out var hash))
{
return Unauthorized("Invalid signature header.");
}
// Según la documentación de MP, el data.id debe estar en minúsculas para la firma.
var manifest = $"id:{resourceId.ToLower()};request-id:{xRequestId};ts:{ts};";
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var computedHashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(manifest));
var computedHash = Convert.ToHexString(computedHashBytes).ToLower();
if (computedHash != hash)
{
return Unauthorized("Signature mismatch.");
}
}
// 3. PROCESAMIENTO (SOLO SI LA FIRMA ES VÁLIDA)
try
{
await _paymentService.ProcessWebhookAsync(resourceTopic, resourceId);
return Ok();
}
catch (Exception ex)
{
Console.WriteLine($"Error procesando webhook validado: {ex.Message}");
return StatusCode(500, "Internal server error processing webhook.");
}
}
[HttpPost("check-status/{adId}")]
public async Task<IActionResult> CheckStatus(int adId)
{
try
{
var result = await _paymentService.CheckPaymentStatusAsync(adId);
return Ok(result);
}
catch (Exception ex)
{
return BadRequest(new { message = ex.Message });
}
}
}