145 lines
5.0 KiB
C#
145 lines
5.0 KiB
C#
|
|
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 });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|