Saltar al contenido principal

C# (.NET)

El ejemplo en C# se divide en dos partes: un endpoint de API en ASP.NET Core que recibe la notificación, y una clase de servicio para la validación criptográfica.

1. Recepción de la Notificación (ASP.NET Core Minimal API):

Este código muestra cómo configurar un endpoint para recibir la notificación de SyPago. Es importante configurar la lectura del cuerpo de la solicitud como un string o byte[] crudo para no alterar su contenido.

// En Program.cs o donde configures tus endpoints
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using System.Text;

// ...

app.MapPost("/webhook-sypago", async (HttpRequest request, [FromServices] ISignatureValidationService validationService) =>
{
// 1. Extraer los componentes de la notificación
if (!request.Headers.TryGetValue("X-Signature", out var signature))
{
return Results.BadRequest("Cabecera X-Signature es requerida");
}

if (!request.Headers.TryGetValue("X-Signature-Nonce", out var nonce))
{
return Results.BadRequest("Cabecera X-Signature-Nonce es requerida");
}

string payload;
using (var reader = new StreamReader(request.Body, Encoding.UTF8))
{
payload = await reader.ReadToEndAsync();
}

// 2. Validar la firma
try
{
await validationService.ValidateSyPagoSignatureAsync(signature.First()!, payload, nonce.First()!);
}
catch (Exception ex)
{
// La firma no es válida. Descartar la petición.
Console.WriteLine($"Error al validar la firma: {ex.Message}");
return Results.Unauthorized();
}

// 3. La firma es válida. Procesar la notificación.
Console.WriteLine("Firma válida. Procesando notificación...");
// Aquí iría la lógica para procesar el payload de la notificación.
// Por ejemplo, deserializar el JSON del payload y actualizar la base de datos.

return Results.Ok(new { message = "Firma válida y notificación recibida" });
});

2. Validación de la Firma

Esta clase de servicio contiene la lógica de validación.

using System.Security.Cryptography;
using System.Text;

public interface ISignatureValidationService
{
Task ValidateSyPagoSignatureAsync(string signatureB64, string payload, string nonce);
}

public class SignatureValidationService : ISignatureValidationService
{
public async Task ValidateSyPagoSignatureAsync(string signatureB64, string payload, string nonce)
{
// --- Obtención de Secretos ---
// El `operationSecret` DEBE ser recuperado de su base de datos.
// Lo guardó cuando inició la transacción con SyPago y recibió el `transaction_id`.
string operationSecret = "9f4aaf08-8d04-4007-a097-c0e95eddad5e"; // ¡EJEMPLO! Reemplazar con su lógica de obtención.

// La `publicKeyPEM` DEBE ser obtenida del endpoint GET /api/v1/user/key
// usando su token JWT. Es recomendable cachear esta clave.
string publicKeyPEM = @"-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9hXXl4g886loHmI10dLrJWFHEB8r
cyqD1hBdIM3ekQkb5YTpOShAu+7xk0fyL/0IBjLEKwSd7rsKrYGtIgJj0w==
-----END PUBLIC KEY-----"; // ¡EJEMPLO! Reemplazar con la clave real obtenida.

// --- Proceso de Verificación ---

// 1. Reconstruir el mensaje a verificar
string stringToVerify = $"{payload}.{nonce}.{operationSecret}";
byte[] dataToVerify = Encoding.UTF8.GetBytes(stringToVerify);

// 2. Calcular el hash SHA-256 del mensaje
byte[] hash = SHA256.HashData(dataToVerify);

// 3. Decodificar la firma de Base64
byte[] signatureBytes = Convert.FromBase64String(signatureB64);

// 4. Cargar la clave pública y verificar la firma
using (var ecdsa = ECDsa.Create())
{
ecdsa.ImportFromPem(publicKeyPEM);

// 5. Verificar la firma del hash usando el formato ASN.1 DER
// DSASignatureFormat.Rfc3279DerSequence es el formato que usa SyPago y es el default para VerifyHash
bool isSignatureValid = ecdsa.VerifyHash(hash, signatureBytes, DSASignatureFormat.Rfc3279DerSequence);

if (!isSignatureValid)
{
throw new CryptographicException("La firma no es válida");
}
}

// La firma es válida
}
}