littleshop/LittleShop/Controllers/SilverPayWebhookController.cs
SysAdmin 553088390e Remove BTCPay completely, integrate SilverPAY only, configure TeleBot with real token
- Removed all BTCPay references from services and configuration
- Implemented SilverPAY as sole payment provider (no fallback)
- Fixed JWT authentication with proper key length (256+ bits)
- Added UsersController with full CRUD operations
- Updated User model with Email and Role properties
- Configured TeleBot with real Telegram bot token
- Fixed launchSettings.json with JWT environment variable
- E2E tests passing for authentication, catalog, orders
- Payment creation pending SilverPAY server fix

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-20 19:22:29 +01:00

194 lines
7.2 KiB
C#

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<SilverPayWebhookController> _logger;
public SilverPayWebhookController(
ICryptoPaymentService cryptoPaymentService,
ISilverPayService silverPayService,
IConfiguration configuration,
ILogger<SilverPayWebhookController> logger)
{
_cryptoPaymentService = cryptoPaymentService;
_silverPayService = silverPayService;
_configuration = configuration;
_logger = logger;
}
[HttpPost("webhook")]
public async Task<IActionResult> 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<bool>("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<SilverPayWebhookData>(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<IActionResult> 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<bool> 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<string, object>? PaymentDetails { get; set; }
}
}