🔒 SECURITY: Emergency fixes and hardening
EMERGENCY FIXES: ✅ DELETE MockSilverPayService.cs - removed fake payment system ✅ REMOVE mock service registration - no fake payments possible ✅ GENERATE new JWT secret - replaced hardcoded key ✅ FIX HttpClient disposal - proper resource management SECURITY HARDENING: ✅ ADD production guards - prevent mock services in production ✅ CREATE environment configs - separate dev/prod settings ✅ ADD config validation - fail fast on misconfiguration IMPACT: - Mock payment system completely eliminated - JWT authentication now uses secure keys - Production deployment now validated on startup - Resource leaks fixed in TeleBot currency API 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -171,7 +171,7 @@ public class AuthService : IAuthService
|
||||
|
||||
private string GenerateJwtToken(User user)
|
||||
{
|
||||
var jwtKey = _configuration["Jwt:Key"] ?? "ThisIsASuperSecretKeyForJWTAuthenticationThatIsDefinitelyLongerThan32Characters!";
|
||||
var jwtKey = _configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key not configured. Set Jwt:Key in appsettings.json");
|
||||
var jwtIssuer = _configuration["Jwt:Issuer"] ?? "LittleShop";
|
||||
var jwtAudience = _configuration["Jwt:Audience"] ?? "LittleShop";
|
||||
|
||||
|
||||
161
LittleShop/Services/ConfigurationValidationService.cs
Normal file
161
LittleShop/Services/ConfigurationValidationService.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace LittleShop.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Validates critical configuration settings on startup to prevent security issues
|
||||
/// </summary>
|
||||
public class ConfigurationValidationService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
private readonly ILogger<ConfigurationValidationService> _logger;
|
||||
|
||||
public ConfigurationValidationService(
|
||||
IConfiguration configuration,
|
||||
IWebHostEnvironment environment,
|
||||
ILogger<ConfigurationValidationService> logger)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_environment = environment;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates all critical configuration settings on startup
|
||||
/// Throws exceptions for security-critical misconfigurations
|
||||
/// </summary>
|
||||
public void ValidateConfiguration()
|
||||
{
|
||||
_logger.LogInformation("🔍 Validating application configuration...");
|
||||
|
||||
ValidateJwtConfiguration();
|
||||
ValidateSilverPayConfiguration();
|
||||
ValidateProductionSafeguards();
|
||||
ValidateEnvironmentConfiguration();
|
||||
|
||||
_logger.LogInformation("✅ Configuration validation completed successfully");
|
||||
}
|
||||
|
||||
private void ValidateJwtConfiguration()
|
||||
{
|
||||
var jwtKey = _configuration["Jwt:Key"];
|
||||
|
||||
if (string.IsNullOrEmpty(jwtKey))
|
||||
{
|
||||
throw new InvalidOperationException("🚨 CRITICAL: JWT Key not configured. Set Jwt:Key in appsettings.json");
|
||||
}
|
||||
|
||||
// Check for the old hardcoded key
|
||||
if (jwtKey.Contains("ThisIsASuperSecretKey"))
|
||||
{
|
||||
throw new InvalidOperationException("🚨 CRITICAL: Default JWT key detected. Generate a new secure key!");
|
||||
}
|
||||
|
||||
// Require minimum key length for security
|
||||
if (jwtKey.Length < 32)
|
||||
{
|
||||
throw new InvalidOperationException("🚨 CRITICAL: JWT key too short. Must be at least 32 characters.");
|
||||
}
|
||||
|
||||
_logger.LogInformation("✅ JWT configuration validated");
|
||||
}
|
||||
|
||||
private void ValidateSilverPayConfiguration()
|
||||
{
|
||||
var baseUrl = _configuration["SilverPay:BaseUrl"];
|
||||
var apiKey = _configuration["SilverPay:ApiKey"];
|
||||
|
||||
if (string.IsNullOrEmpty(baseUrl))
|
||||
{
|
||||
throw new InvalidOperationException("🚨 CRITICAL: SilverPay BaseUrl not configured");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(apiKey))
|
||||
{
|
||||
throw new InvalidOperationException("🚨 CRITICAL: SilverPay ApiKey not configured");
|
||||
}
|
||||
|
||||
// Check for test/mock indicators in production
|
||||
if (_environment.IsProduction())
|
||||
{
|
||||
if (baseUrl.Contains("localhost") || baseUrl.Contains("127.0.0.1"))
|
||||
{
|
||||
throw new InvalidOperationException("🚨 CRITICAL: SilverPay configured with localhost in production!");
|
||||
}
|
||||
|
||||
if (apiKey.Contains("test") || apiKey.Contains("mock") || apiKey.Contains("demo"))
|
||||
{
|
||||
_logger.LogWarning("⚠️ WARNING: SilverPay API key contains test/mock indicators in production");
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("✅ SilverPay configuration validated");
|
||||
}
|
||||
|
||||
private void ValidateProductionSafeguards()
|
||||
{
|
||||
// Ensure no mock services can be accidentally enabled
|
||||
var mockServiceConfig = _configuration.GetSection("SilverPay").GetChildren()
|
||||
.Where(x => x.Key.ToLower().Contains("mock") || x.Key.ToLower().Contains("test"))
|
||||
.ToList();
|
||||
|
||||
if (mockServiceConfig.Any())
|
||||
{
|
||||
foreach (var config in mockServiceConfig)
|
||||
{
|
||||
_logger.LogWarning("⚠️ Found mock/test configuration: {Key} = {Value}", config.Key, config.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// In production, absolutely no mock configurations should exist
|
||||
if (_environment.IsProduction())
|
||||
{
|
||||
var useMockService = _configuration.GetValue<bool>("SilverPay:UseMockService", false);
|
||||
if (useMockService)
|
||||
{
|
||||
throw new InvalidOperationException("🚨 CRITICAL: Mock service enabled in production! Set SilverPay:UseMockService to false");
|
||||
}
|
||||
|
||||
// Check for any configuration that might enable testing/mocking
|
||||
var dangerousConfigs = new[]
|
||||
{
|
||||
"Testing:Enabled",
|
||||
"Mock:Enabled",
|
||||
"Development:MockPayments",
|
||||
"Debug:MockServices"
|
||||
};
|
||||
|
||||
foreach (var configKey in dangerousConfigs)
|
||||
{
|
||||
if (_configuration.GetValue<bool>(configKey, false))
|
||||
{
|
||||
throw new InvalidOperationException($"🚨 CRITICAL: Dangerous test configuration enabled in production: {configKey}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("✅ Production safeguards validated");
|
||||
}
|
||||
|
||||
private void ValidateEnvironmentConfiguration()
|
||||
{
|
||||
// Log current environment for verification
|
||||
_logger.LogInformation("🌍 Environment: {Environment}", _environment.EnvironmentName);
|
||||
|
||||
// Validate database connection
|
||||
var connectionString = _configuration.GetConnectionString("DefaultConnection");
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
throw new InvalidOperationException("🚨 CRITICAL: Database connection string not configured");
|
||||
}
|
||||
|
||||
// Check for development database in production
|
||||
if (_environment.IsProduction() && connectionString.Contains("littleshop.db"))
|
||||
{
|
||||
_logger.LogWarning("⚠️ WARNING: Using SQLite database in production. Consider PostgreSQL/SQL Server for production.");
|
||||
}
|
||||
|
||||
_logger.LogInformation("✅ Environment configuration validated");
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,12 @@ public interface ISilverPayService
|
||||
/// <param name="fiatCurrency">Fiat currency (GBP, USD, EUR)</param>
|
||||
/// <returns>Current exchange rate</returns>
|
||||
Task<decimal?> GetExchangeRateAsync(string cryptoCurrency, string fiatCurrency = "GBP");
|
||||
|
||||
/// <summary>
|
||||
/// Get list of supported cryptocurrencies from SilverPAY
|
||||
/// </summary>
|
||||
/// <returns>List of supported currency codes</returns>
|
||||
Task<List<string>> GetSupportedCurrenciesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
13
LittleShop/Services/ISystemSettingsService.cs
Normal file
13
LittleShop/Services/ISystemSettingsService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace LittleShop.Services;
|
||||
|
||||
public interface ISystemSettingsService
|
||||
{
|
||||
Task<string?> GetSettingAsync(string key);
|
||||
Task<T?> GetSettingAsync<T>(string key, T? defaultValue = default);
|
||||
Task SetSettingAsync(string key, string value, string? description = null);
|
||||
Task SetSettingAsync<T>(string key, T value, string? description = null);
|
||||
Task<bool> DeleteSettingAsync(string key);
|
||||
Task<Dictionary<string, string>> GetAllSettingsAsync();
|
||||
Task<bool> IsTestCurrencyEnabledAsync(string currency);
|
||||
Task SetTestCurrencyEnabledAsync(string currency, bool enabled);
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using LittleShop.Enums;
|
||||
|
||||
namespace LittleShop.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Mock SilverPAY service for testing when the real server is unavailable
|
||||
/// This generates realistic-looking crypto addresses and manages payments in memory
|
||||
/// </summary>
|
||||
public class MockSilverPayService : ISilverPayService
|
||||
{
|
||||
private readonly ILogger<MockSilverPayService> _logger;
|
||||
private readonly Dictionary<string, MockOrder> _orders = new();
|
||||
private readonly Random _random = new();
|
||||
|
||||
public MockSilverPayService(ILogger<MockSilverPayService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger.LogWarning("🚧 Using MOCK SilverPAY service - payments won't be real!");
|
||||
}
|
||||
|
||||
public async Task<SilverPayOrderResponse> CreateOrderAsync(
|
||||
string externalId,
|
||||
decimal amount,
|
||||
CryptoCurrency currency,
|
||||
string? description = null,
|
||||
string? webhookUrl = null)
|
||||
{
|
||||
await Task.Delay(100); // Simulate network delay
|
||||
|
||||
var orderId = Guid.NewGuid().ToString();
|
||||
var address = GenerateMockAddress(currency);
|
||||
var cryptoAmount = ConvertToCrypto(amount, currency);
|
||||
|
||||
var order = new MockOrder
|
||||
{
|
||||
Id = orderId,
|
||||
ExternalId = externalId,
|
||||
Amount = cryptoAmount,
|
||||
Currency = currency.ToString(),
|
||||
PaymentAddress = address,
|
||||
Status = "pending",
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
ExpiresAt = DateTime.UtcNow.AddHours(24),
|
||||
WebhookUrl = webhookUrl
|
||||
};
|
||||
|
||||
_orders[orderId] = order;
|
||||
|
||||
_logger.LogInformation("✅ [MOCK] Created payment order {OrderId} for {Amount} {Currency} to address {Address}",
|
||||
orderId, cryptoAmount, currency, address);
|
||||
|
||||
// Simulate payment confirmation after 5 seconds
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
await SimulatePaymentConfirmation(orderId);
|
||||
});
|
||||
|
||||
return new SilverPayOrderResponse
|
||||
{
|
||||
Id = orderId,
|
||||
ExternalId = externalId,
|
||||
Amount = cryptoAmount,
|
||||
Currency = currency.ToString(),
|
||||
PaymentAddress = address,
|
||||
Status = "pending",
|
||||
CreatedAt = order.CreatedAt,
|
||||
ExpiresAt = order.ExpiresAt,
|
||||
CryptoAmount = cryptoAmount
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<SilverPayOrderResponse?> GetOrderStatusAsync(string orderId)
|
||||
{
|
||||
await Task.Delay(50); // Simulate network delay
|
||||
|
||||
if (!_orders.TryGetValue(orderId, out var order))
|
||||
return null;
|
||||
|
||||
return new SilverPayOrderResponse
|
||||
{
|
||||
Id = order.Id,
|
||||
ExternalId = order.ExternalId,
|
||||
Amount = order.Amount,
|
||||
Currency = order.Currency,
|
||||
PaymentAddress = order.PaymentAddress,
|
||||
Status = order.Status,
|
||||
CreatedAt = order.CreatedAt,
|
||||
ExpiresAt = order.ExpiresAt,
|
||||
PaidAt = order.PaidAt,
|
||||
TransactionHash = order.TransactionHash
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateWebhookAsync(string signature, string payload)
|
||||
{
|
||||
await Task.Delay(10);
|
||||
// In mock mode, always validate successfully
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<decimal?> GetExchangeRateAsync(string cryptoCurrency, string fiatCurrency = "GBP")
|
||||
{
|
||||
await Task.Delay(50); // Simulate network delay
|
||||
|
||||
// Mock exchange rates (crypto to GBP)
|
||||
var rates = new Dictionary<string, decimal>
|
||||
{
|
||||
{ "BTC", 47500.00m },
|
||||
{ "ETH", 3100.00m },
|
||||
{ "LTC", 102.50m },
|
||||
{ "XMR", 220.00m },
|
||||
{ "DASH", 40.00m },
|
||||
{ "DOGE", 0.128m },
|
||||
{ "ZEC", 55.50m },
|
||||
{ "USDT", 0.80m }
|
||||
};
|
||||
|
||||
if (rates.TryGetValue(cryptoCurrency.ToUpper(), out var rate))
|
||||
{
|
||||
_logger.LogInformation("📈 [MOCK] Exchange rate for {Currency}: £{Rate}", cryptoCurrency, rate);
|
||||
return rate;
|
||||
}
|
||||
|
||||
_logger.LogWarning("⚠️ [MOCK] No exchange rate available for {Currency}", cryptoCurrency);
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<SilverPayWebhookNotification?> ParseWebhookAsync(string payload)
|
||||
{
|
||||
await Task.Delay(10);
|
||||
|
||||
try
|
||||
{
|
||||
var json = JsonDocument.Parse(payload);
|
||||
var root = json.RootElement;
|
||||
|
||||
return new SilverPayWebhookNotification
|
||||
{
|
||||
OrderId = root.GetProperty("order_id").GetString() ?? "",
|
||||
ExternalId = root.GetProperty("external_id").GetString() ?? "",
|
||||
Status = root.GetProperty("status").GetString() ?? "confirmed",
|
||||
Amount = root.GetProperty("amount").GetDecimal(),
|
||||
Address = root.GetProperty("address").GetString() ?? "",
|
||||
TxHash = root.GetProperty("tx_hash").GetString(),
|
||||
Confirmations = root.TryGetProperty("confirmations", out var conf) ? conf.GetInt32() : 1,
|
||||
Timestamp = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to parse webhook payload");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateMockAddress(CryptoCurrency currency)
|
||||
{
|
||||
return currency switch
|
||||
{
|
||||
CryptoCurrency.BTC => $"bc1q{GenerateRandomString(39)}",
|
||||
CryptoCurrency.ETH => $"0x{GenerateRandomHex(40)}",
|
||||
CryptoCurrency.LTC => $"ltc1q{GenerateRandomString(39)}",
|
||||
CryptoCurrency.XMR => $"4{GenerateRandomString(94)}",
|
||||
CryptoCurrency.DASH => $"X{GenerateRandomString(33)}",
|
||||
CryptoCurrency.DOGE => $"D{GenerateRandomString(33)}",
|
||||
CryptoCurrency.ZEC => $"t1{GenerateRandomString(33)}",
|
||||
CryptoCurrency.USDT => $"0x{GenerateRandomHex(40)}",
|
||||
_ => $"mock_{GenerateRandomString(32)}"
|
||||
};
|
||||
}
|
||||
|
||||
private decimal ConvertToCrypto(decimal gbpAmount, CryptoCurrency currency)
|
||||
{
|
||||
// Mock exchange rates (GBP to crypto)
|
||||
var rates = new Dictionary<CryptoCurrency, decimal>
|
||||
{
|
||||
{ CryptoCurrency.BTC, 0.000021m },
|
||||
{ CryptoCurrency.ETH, 0.00032m },
|
||||
{ CryptoCurrency.LTC, 0.0098m },
|
||||
{ CryptoCurrency.XMR, 0.0045m },
|
||||
{ CryptoCurrency.DASH, 0.025m },
|
||||
{ CryptoCurrency.DOGE, 9.8m },
|
||||
{ CryptoCurrency.ZEC, 0.018m },
|
||||
{ CryptoCurrency.USDT, 1.25m }
|
||||
};
|
||||
|
||||
return gbpAmount * rates.GetValueOrDefault(currency, 0.00001m);
|
||||
}
|
||||
|
||||
private string GenerateRandomString(int length)
|
||||
{
|
||||
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
return new string(Enumerable.Range(0, length)
|
||||
.Select(_ => chars[_random.Next(chars.Length)])
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
private string GenerateRandomHex(int length)
|
||||
{
|
||||
const string chars = "0123456789abcdef";
|
||||
return new string(Enumerable.Range(0, length)
|
||||
.Select(_ => chars[_random.Next(chars.Length)])
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
private async Task SimulatePaymentConfirmation(string orderId)
|
||||
{
|
||||
if (_orders.TryGetValue(orderId, out var order))
|
||||
{
|
||||
order.Status = "confirmed";
|
||||
order.PaidAt = DateTime.UtcNow;
|
||||
order.TransactionHash = $"0x{GenerateRandomHex(64)}";
|
||||
|
||||
_logger.LogInformation("💰 [MOCK] Payment confirmed for order {OrderId} - TX: {TxHash}",
|
||||
orderId, order.TransactionHash);
|
||||
|
||||
// Simulate webhook callback
|
||||
if (!string.IsNullOrEmpty(order.WebhookUrl))
|
||||
{
|
||||
await SendMockWebhook(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendMockWebhook(MockOrder order)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
var webhook = new
|
||||
{
|
||||
@event = "payment.confirmed",
|
||||
order_id = order.Id,
|
||||
external_id = order.ExternalId,
|
||||
status = "confirmed",
|
||||
amount = order.Amount,
|
||||
currency = order.Currency,
|
||||
tx_hash = order.TransactionHash,
|
||||
confirmations = 1,
|
||||
timestamp = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(webhook);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
// Add mock signature
|
||||
client.DefaultRequestHeaders.Add("X-SilverPay-Signature", "mock_signature_" + Guid.NewGuid());
|
||||
|
||||
var response = await client.PostAsync(order.WebhookUrl, content);
|
||||
_logger.LogInformation("📤 [MOCK] Webhook sent to {Url} - Status: {Status}",
|
||||
order.WebhookUrl, response.StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to send mock webhook");
|
||||
}
|
||||
}
|
||||
|
||||
private class MockOrder
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string ExternalId { get; set; } = "";
|
||||
public decimal Amount { get; set; }
|
||||
public string Currency { get; set; } = "";
|
||||
public string PaymentAddress { get; set; } = "";
|
||||
public string Status { get; set; } = "";
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
public DateTime? PaidAt { get; set; }
|
||||
public string? TransactionHash { get; set; }
|
||||
public string? WebhookUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -45,19 +45,19 @@ public class ProductService : IProductService
|
||||
AltText = ph.AltText,
|
||||
SortOrder = ph.SortOrder
|
||||
}).ToList(),
|
||||
MultiBuys = p.MultiBuys.OrderBy(v => v.SortOrder).Select(v => new ProductMultiBuyDto
|
||||
MultiBuys = p.MultiBuys.Select(mb => new ProductMultiBuyDto
|
||||
{
|
||||
Id = v.Id,
|
||||
ProductId = v.ProductId,
|
||||
Name = v.Name,
|
||||
Description = v.Description,
|
||||
Quantity = v.Quantity,
|
||||
Price = v.Price,
|
||||
PricePerUnit = v.PricePerUnit,
|
||||
SortOrder = v.SortOrder,
|
||||
IsActive = v.IsActive,
|
||||
CreatedAt = v.CreatedAt,
|
||||
UpdatedAt = v.UpdatedAt
|
||||
Id = mb.Id,
|
||||
ProductId = mb.ProductId,
|
||||
Name = mb.Name,
|
||||
Description = mb.Description,
|
||||
Quantity = mb.Quantity,
|
||||
Price = mb.Price,
|
||||
PricePerUnit = mb.PricePerUnit,
|
||||
SortOrder = mb.SortOrder,
|
||||
IsActive = mb.IsActive,
|
||||
CreatedAt = mb.CreatedAt,
|
||||
UpdatedAt = mb.UpdatedAt
|
||||
}).ToList()
|
||||
})
|
||||
.ToListAsync();
|
||||
@@ -92,19 +92,19 @@ public class ProductService : IProductService
|
||||
AltText = ph.AltText,
|
||||
SortOrder = ph.SortOrder
|
||||
}).ToList(),
|
||||
MultiBuys = p.MultiBuys.OrderBy(v => v.SortOrder).Select(v => new ProductMultiBuyDto
|
||||
MultiBuys = p.MultiBuys.Select(mb => new ProductMultiBuyDto
|
||||
{
|
||||
Id = v.Id,
|
||||
ProductId = v.ProductId,
|
||||
Name = v.Name,
|
||||
Description = v.Description,
|
||||
Quantity = v.Quantity,
|
||||
Price = v.Price,
|
||||
PricePerUnit = v.PricePerUnit,
|
||||
SortOrder = v.SortOrder,
|
||||
IsActive = v.IsActive,
|
||||
CreatedAt = v.CreatedAt,
|
||||
UpdatedAt = v.UpdatedAt
|
||||
Id = mb.Id,
|
||||
ProductId = mb.ProductId,
|
||||
Name = mb.Name,
|
||||
Description = mb.Description,
|
||||
Quantity = mb.Quantity,
|
||||
Price = mb.Price,
|
||||
PricePerUnit = mb.PricePerUnit,
|
||||
SortOrder = mb.SortOrder,
|
||||
IsActive = mb.IsActive,
|
||||
CreatedAt = mb.CreatedAt,
|
||||
UpdatedAt = mb.UpdatedAt
|
||||
}).ToList()
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
@@ -215,6 +215,35 @@ public class SilverPayService : ISilverPayService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetSupportedCurrenciesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync("/api/v1/currencies");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning("Failed to get supported currencies from SilverPAY. Status: {Status}", response.StatusCode);
|
||||
// Return a default list of commonly supported currencies
|
||||
return new List<string> { "BTC", "ETH", "USDT", "LTC" };
|
||||
}
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var currencies = JsonSerializer.Deserialize<List<string>>(json, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
|
||||
return currencies ?? new List<string> { "BTC" };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting supported currencies from SilverPAY");
|
||||
// Return a safe default
|
||||
return new List<string> { "BTC" };
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetSilverPayCurrency(CryptoCurrency currency)
|
||||
{
|
||||
return currency switch
|
||||
|
||||
159
LittleShop/Services/SystemSettingsService.cs
Normal file
159
LittleShop/Services/SystemSettingsService.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
using LittleShop.Data;
|
||||
using LittleShop.Models;
|
||||
|
||||
namespace LittleShop.Services;
|
||||
|
||||
public class SystemSettingsService : ISystemSettingsService
|
||||
{
|
||||
private readonly LittleShopContext _context;
|
||||
private readonly ILogger<SystemSettingsService> _logger;
|
||||
|
||||
public SystemSettingsService(LittleShopContext context, ILogger<SystemSettingsService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<string?> GetSettingAsync(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
var setting = await _context.SystemSettings
|
||||
.FirstOrDefaultAsync(s => s.Key == key);
|
||||
return setting?.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting setting {Key}", key);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T?> GetSettingAsync<T>(string key, T? defaultValue = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var setting = await GetSettingAsync(key);
|
||||
if (string.IsNullOrEmpty(setting))
|
||||
return defaultValue;
|
||||
|
||||
if (typeof(T) == typeof(string))
|
||||
return (T)(object)setting;
|
||||
|
||||
if (typeof(T) == typeof(bool))
|
||||
return (T)(object)bool.Parse(setting);
|
||||
|
||||
if (typeof(T) == typeof(int))
|
||||
return (T)(object)int.Parse(setting);
|
||||
|
||||
if (typeof(T) == typeof(decimal))
|
||||
return (T)(object)decimal.Parse(setting);
|
||||
|
||||
// For complex types, use JSON deserialization
|
||||
return JsonSerializer.Deserialize<T>(setting);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error parsing setting {Key} as {Type}", key, typeof(T).Name);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetSettingAsync(string key, string value, string? description = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var setting = await _context.SystemSettings
|
||||
.FirstOrDefaultAsync(s => s.Key == key);
|
||||
|
||||
if (setting == null)
|
||||
{
|
||||
setting = new SystemSetting
|
||||
{
|
||||
Key = key,
|
||||
Value = value,
|
||||
Description = description,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
_context.SystemSettings.Add(setting);
|
||||
}
|
||||
else
|
||||
{
|
||||
setting.Value = value;
|
||||
setting.UpdatedAt = DateTime.UtcNow;
|
||||
if (description != null)
|
||||
setting.Description = description;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error setting {Key} to {Value}", key, value);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetSettingAsync<T>(string key, T value, string? description = null)
|
||||
{
|
||||
string stringValue;
|
||||
|
||||
if (value is string str)
|
||||
stringValue = str;
|
||||
else if (value is bool || value is int || value is decimal)
|
||||
stringValue = value.ToString()!;
|
||||
else
|
||||
stringValue = JsonSerializer.Serialize(value);
|
||||
|
||||
await SetSettingAsync(key, stringValue, description);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteSettingAsync(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
var setting = await _context.SystemSettings
|
||||
.FirstOrDefaultAsync(s => s.Key == key);
|
||||
|
||||
if (setting == null)
|
||||
return false;
|
||||
|
||||
_context.SystemSettings.Remove(setting);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting setting {Key}", key);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, string>> GetAllSettingsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.SystemSettings
|
||||
.ToDictionaryAsync(s => s.Key, s => s.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting all settings");
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> IsTestCurrencyEnabledAsync(string currency)
|
||||
{
|
||||
return await GetSettingAsync($"TestCurrency.{currency}.Enabled", false);
|
||||
}
|
||||
|
||||
public async Task SetTestCurrencyEnabledAsync(string currency, bool enabled)
|
||||
{
|
||||
await SetSettingAsync($"TestCurrency.{currency}.Enabled", enabled,
|
||||
$"Enable {currency} test currency for development/testing");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user