using Microsoft.AspNetCore.Mvc; using System.Text; using System.Text.Json; using LittleShop.Services; using LittleShop.Enums; namespace LittleShop.Controllers; [ApiController] [Route("api/silverpay")] public class SilverPayWebhookController : ControllerBase { private readonly ICryptoPaymentService _cryptoPaymentService; private readonly ISilverPayService _silverPayService; private readonly IConfiguration _configuration; private readonly ILogger _logger; public SilverPayWebhookController( ICryptoPaymentService cryptoPaymentService, ISilverPayService silverPayService, IConfiguration configuration, ILogger logger) { _cryptoPaymentService = cryptoPaymentService; _silverPayService = silverPayService; _configuration = configuration; _logger = logger; } [HttpPost("webhook")] public async Task ProcessWebhook() { try { // Read the raw request body using var reader = new StreamReader(Request.Body); var requestBody = await reader.ReadToEndAsync(); // Get webhook signature from headers var signature = Request.Headers["X-SilverPay-Signature"].FirstOrDefault() ?? Request.Headers["X-Webhook-Signature"].FirstOrDefault(); if (string.IsNullOrEmpty(signature)) { _logger.LogWarning("SilverPAY webhook received without signature"); // For development, we might allow unsigned webhooks if (!_configuration.GetValue("SilverPay:AllowUnsignedWebhooks", false)) { return BadRequest("Missing webhook signature"); } } else { // Validate webhook signature var isValid = await _silverPayService.ValidateWebhookAsync(requestBody, signature); if (!isValid) { _logger.LogWarning("Invalid SilverPAY webhook signature"); return BadRequest("Invalid webhook signature"); } } // Parse webhook data var webhookData = JsonSerializer.Deserialize(requestBody, new JsonSerializerOptions { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }); if (webhookData == null) { _logger.LogWarning("Unable to parse SilverPAY webhook data"); return BadRequest("Invalid webhook data"); } _logger.LogInformation("Processing SilverPAY webhook: OrderId={OrderId}, Status={Status}, TxHash={TxHash}, Confirmations={Confirmations}", webhookData.OrderId, webhookData.Status, webhookData.TxHash, webhookData.Confirmations); // Process the webhook based on status var success = await ProcessWebhookEvent(webhookData); if (!success) { return BadRequest("Failed to process webhook"); } return Ok(new { status = "received", processed = true }); } catch (Exception ex) { _logger.LogError(ex, "Error processing SilverPAY webhook"); return StatusCode(500, "Internal server error"); } } [HttpPost("webhook/{provider}")] public async Task ProcessProviderWebhook(string provider) { // This endpoint handles provider-specific webhooks from SilverPAY // (e.g., notifications from specific blockchain APIs) try { using var reader = new StreamReader(Request.Body); var requestBody = await reader.ReadToEndAsync(); _logger.LogInformation("Received SilverPAY provider webhook from {Provider}", provider); _logger.LogDebug("Provider webhook payload: {Payload}", requestBody); // For now, just acknowledge receipt // The actual processing would depend on the provider's format return Ok(new { status = "received", provider }); } catch (Exception ex) { _logger.LogError(ex, "Error processing SilverPAY provider webhook from {Provider}", provider); return StatusCode(500, "Internal server error"); } } private async Task ProcessWebhookEvent(SilverPayWebhookData webhookData) { try { // Map SilverPAY status to our payment status var paymentStatus = MapSilverPayStatus(webhookData.Status); if (!paymentStatus.HasValue) { _logger.LogInformation("Ignoring SilverPAY webhook status: {Status}", webhookData.Status); return true; // Not an error, just not a status we care about } // Process the payment update var success = await _cryptoPaymentService.ProcessSilverPayWebhookAsync( webhookData.OrderId, paymentStatus.Value, webhookData.Amount, webhookData.TxHash, webhookData.Confirmations); if (success) { _logger.LogInformation("Successfully processed SilverPAY webhook for order {OrderId} with status {Status}", webhookData.OrderId, paymentStatus.Value); } else { _logger.LogWarning("Failed to process SilverPAY webhook for order {OrderId}", webhookData.OrderId); } return success; } catch (Exception ex) { _logger.LogError(ex, "Error processing SilverPAY webhook event for order {OrderId}", webhookData.OrderId); return false; } } private static PaymentStatus? MapSilverPayStatus(string status) { return status?.ToLowerInvariant() switch { "pending" => PaymentStatus.Pending, "waiting" => PaymentStatus.Pending, "unconfirmed" => PaymentStatus.Processing, "confirming" => PaymentStatus.Processing, "partially_paid" => PaymentStatus.Processing, "paid" => PaymentStatus.Paid, "confirmed" => PaymentStatus.Completed, "completed" => PaymentStatus.Completed, "expired" => PaymentStatus.Expired, "failed" => PaymentStatus.Failed, "cancelled" => PaymentStatus.Failed, _ => null // Unknown status }; } // Internal class for JSON deserialization private class SilverPayWebhookData { public string OrderId { get; set; } = string.Empty; public string ExternalId { get; set; } = string.Empty; public string Status { get; set; } = string.Empty; public string Address { get; set; } = string.Empty; public string? TxHash { get; set; } public decimal Amount { get; set; } public int Confirmations { get; set; } public int? BlockHeight { get; set; } public DateTime Timestamp { get; set; } public string? Currency { get; set; } public Dictionary? PaymentDetails { get; set; } } }