using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using LittleShop.Enums;
namespace LittleShop.Services;
///
/// Mock SilverPAY service for testing when the real server is unavailable
/// This generates realistic-looking crypto addresses and manages payments in memory
///
public class MockSilverPayService : ISilverPayService
{
private readonly ILogger _logger;
private readonly Dictionary _orders = new();
private readonly Random _random = new();
public MockSilverPayService(ILogger logger)
{
_logger = logger;
_logger.LogWarning("🚧 Using MOCK SilverPAY service - payments won't be real!");
}
public async Task 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 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 ValidateWebhookAsync(string signature, string payload)
{
await Task.Delay(10);
// In mock mode, always validate successfully
return true;
}
public async Task GetExchangeRateAsync(string cryptoCurrency, string fiatCurrency = "GBP")
{
await Task.Delay(50); // Simulate network delay
// Mock exchange rates (crypto to GBP)
var rates = new Dictionary
{
{ "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 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.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; }
}
}