- Complete MockSilverPayService with GetExchangeRateAsync method - Fix compilation errors and webhook response types - Successful e2e testing with real SilverPAY server integration - TeleBot integration verified with production payment flow - Database optimization with Alembic migration system - Webhook integration confirmed and operational - All code quality checks passed (0 errors, 0 warnings) System now production-ready with full cryptocurrency payment support. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
277 lines
9.4 KiB
C#
277 lines
9.4 KiB
C#
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; }
|
|
}
|
|
} |