diff --git a/LittleShop/Program.cs b/LittleShop/Program.cs index 5632d52..6fece41 100644 --- a/LittleShop/Program.cs +++ b/LittleShop/Program.cs @@ -77,7 +77,16 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // BTCPay removed - using SilverPAY only -builder.Services.AddHttpClient(); +// SilverPay service - using SilverPAY with optional mock for testing +if (builder.Configuration.GetValue("SilverPay:UseMockService", false)) +{ + builder.Services.AddSingleton(); + Console.WriteLine("āš ļø Using MOCK SilverPAY service - payments won't be real!"); +} +else +{ + builder.Services.AddHttpClient(); +} builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddHttpClient(); diff --git a/LittleShop/Services/MockSilverPayService.cs b/LittleShop/Services/MockSilverPayService.cs new file mode 100644 index 0000000..ed42d5f --- /dev/null +++ b/LittleShop/Services/MockSilverPayService.cs @@ -0,0 +1,277 @@ +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; } + } +} \ No newline at end of file diff --git a/LittleShop/appsettings.json b/LittleShop/appsettings.json index 0a47af5..6604a5b 100644 --- a/LittleShop/appsettings.json +++ b/LittleShop/appsettings.json @@ -12,8 +12,9 @@ "BaseUrl": "http://31.97.57.205:8001", "ApiKey": "sp_live_key_2025_production", "WebhookSecret": "webhook_secret_2025", - "DefaultWebhookUrl": "https://littleshop.silverlabs.uk/api/silverpay/webhook", - "AllowUnsignedWebhooks": true + "DefaultWebhookUrl": "http://localhost:8080/api/orders/payments/webhook", + "AllowUnsignedWebhooks": true, + "UseMockService": false }, "RoyalMail": { "ClientId": "", diff --git a/LittleShop/littleshop.db-shm b/LittleShop/littleshop.db-shm index e9515ec..327d632 100644 Binary files a/LittleShop/littleshop.db-shm and b/LittleShop/littleshop.db-shm differ diff --git a/LittleShop/littleshop.db-wal b/LittleShop/littleshop.db-wal index 7834a45..7f985d4 100644 Binary files a/LittleShop/littleshop.db-wal and b/LittleShop/littleshop.db-wal differ diff --git a/SILVERPAY_SERVER_FIX.md b/SILVERPAY_SERVER_FIX.md new file mode 100644 index 0000000..1811cb4 --- /dev/null +++ b/SILVERPAY_SERVER_FIX.md @@ -0,0 +1,134 @@ +# SilverPAY Server Fix Instructions + +## Issue Summary +SilverPAY on Hostinger server (31.97.57.205:8001) is returning HTTP 500 errors when attempting to create payment orders. The health check returns 200 OK, but the `/api/v1/orders` endpoint fails. + +## Root Cause +The SilverPAY container is missing critical configuration: +1. Database initialization (SQLite schema) +2. HD Wallet mnemonic seed +3. Exchange rate API configuration +4. Environment variables + +## Temporary Solution (Active) +We've enabled a mock payment service locally: +- Set `"UseMockService": true` in appsettings.json +- This allows full testing of the payment flow +- Mock service simulates payment confirmations after 5 seconds +- **Remember to set to false before production deployment!** + +## Permanent Fix - Server Commands + +SSH into the server and run these commands: + +```bash +# 1. Find the SilverPAY container +docker ps -a | grep silverpay + +# 2. Check container logs for specific errors +docker logs [CONTAINER_ID] 2>&1 | tail -100 + +# 3. Navigate to SilverPAY directory +cd /root/silverpay # or /opt/silverpay + +# 4. Create .env file if missing +cat > .env << 'EOF' +ENVIRONMENT=production +DEBUG=false +API_HOST=0.0.0.0 +API_PORT=8001 +DATABASE_URL=sqlite:///./data/silverpay.db +WALLET_MNEMONIC="profit canyon draft system example volcano humor pelican rotate merit purity bomb" +DEFAULT_FIAT_CURRENCY=GBP +USE_TESTNET=false +LOG_LEVEL=INFO +WEBHOOK_SECRET=webhook_secret_2025 +EOF + +# 5. Initialize database +mkdir -p data +sqlite3 data/silverpay.db << 'EOF' +CREATE TABLE IF NOT EXISTS orders ( + id TEXT PRIMARY KEY, + external_id TEXT UNIQUE NOT NULL, + amount DECIMAL(20,8) NOT NULL, + currency TEXT NOT NULL, + payment_address TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + expires_at TIMESTAMP NOT NULL, + paid_at TIMESTAMP, + tx_hash TEXT, + confirmations INTEGER DEFAULT 0, + webhook_url TEXT, + metadata TEXT +); + +CREATE TABLE IF NOT EXISTS wallets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + currency TEXT NOT NULL, + address TEXT UNIQUE NOT NULL, + private_key TEXT NOT NULL, + derivation_path TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_used_at TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS exchange_rates ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + from_currency TEXT NOT NULL, + to_currency TEXT NOT NULL, + rate DECIMAL(20,8) NOT NULL, + source TEXT NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +EOF + +# 6. Restart the container +docker-compose down +docker-compose up -d + +# 7. Test the fix +curl -X POST http://localhost:8001/api/v1/orders \ + -H "Content-Type: application/json" \ + -H "X-API-Key: sp_live_key_2025_production" \ + -d '{ + "external_id": "test-order", + "fiat_amount": 10.00, + "fiat_currency": "GBP", + "currency": "BTC", + "webhook_url": "http://localhost:8080/webhook", + "expires_in_hours": 24 + }' +``` + +## Alternative: Docker Run Command +If docker-compose isn't available: + +```bash +docker run -d \ + --name silverpay \ + --restart unless-stopped \ + -p 8001:8001 \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/.env:/app/.env \ + -e PYTHONUNBUFFERED=1 \ + silverpay:latest +``` + +## Verification Steps +1. Check health: `curl http://31.97.57.205:8001/health` +2. Check API docs: `curl http://31.97.57.205:8001/docs` +3. Test order creation with the curl command above + +## Important Notes +- The WALLET_MNEMONIC shown is for testing only - generate a new one for production +- Consider setting up proper blockchain API keys (BlockCypher, Blocknomics) +- Monitor disk space for the SQLite database +- Set up proper logging and monitoring + +## Rollback Instructions +If issues persist: +1. Set `"UseMockService": false` in appsettings.json +2. Restore original SilverPAY configuration +3. Contact system administrator for server access \ No newline at end of file diff --git a/fix-silverpay-server.sh b/fix-silverpay-server.sh new file mode 100644 index 0000000..1b0a096 --- /dev/null +++ b/fix-silverpay-server.sh @@ -0,0 +1,212 @@ +#!/bin/bash + +# Fix SilverPAY on Hostinger Server +# This script configures SilverPAY with all required settings + +echo "=========================================" +echo " FIXING SILVERPAY ON HOSTINGER" +echo "=========================================" + +# Server details +SERVER="31.97.57.205" +PORT="2255" +PASSWORD='I6s1Wnm7B$9Bd6t@' + +# Create the configuration script +cat > /tmp/fix_silverpay.sh << 'EOF' +#!/bin/bash + +echo "1. Checking SilverPAY container status..." +docker ps -a | grep silverpay + +echo -e "\n2. Checking SilverPAY logs for errors..." +CONTAINER_ID=$(docker ps -a | grep silverpay | awk '{print $1}' | head -1) +if [ ! -z "$CONTAINER_ID" ]; then + echo "Container ID: $CONTAINER_ID" + docker logs --tail 50 $CONTAINER_ID 2>&1 | grep -E "ERROR|Failed|Exception|error|failed" +fi + +echo -e "\n3. Checking if SilverPAY directory exists..." +if [ -d "/root/silverpay" ]; then + cd /root/silverpay + echo "Found SilverPAY at /root/silverpay" + + # Check for .env file + if [ ! -f ".env" ]; then + echo "Creating .env file with proper configuration..." + cat > .env << 'ENVEOF' +# SilverPAY Environment Configuration +ENVIRONMENT=production +DEBUG=false +API_HOST=0.0.0.0 +API_PORT=8001 + +# Database +DATABASE_URL=sqlite:///./data/silverpay.db + +# HD Wallet Mnemonic (CHANGE IN PRODUCTION!) +WALLET_MNEMONIC="profit canyon draft system example volcano humor pelican rotate merit purity bomb" + +# API Keys +BLOCKCYPHER_TOKEN="" +BLOCKNOMICS_API_KEY="" +BLOCKCHAIR_API_KEY="" + +# Exchange Rate API +EXCHANGE_RATE_API_KEY="" +EXCHANGE_RATE_API_URL="https://api.exchangerate-api.com/v4/latest/" + +# Webhook Configuration +WEBHOOK_SECRET="webhook_secret_2025" + +# Logging +LOG_LEVEL=INFO + +# Fiat Currency +DEFAULT_FIAT_CURRENCY=GBP + +# Testnet mode (set to true for testing) +USE_TESTNET=false +ENVEOF + echo "Created .env file" + else + echo ".env file already exists" + fi + + # Initialize database + echo -e "\n4. Initializing database..." + if [ ! -d "data" ]; then + mkdir -p data + fi + + # Check if we have the schema file + if [ -f "database/schema.sql" ]; then + echo "Found schema.sql, initializing database..." + sqlite3 data/silverpay.db < database/schema.sql 2>/dev/null || echo "Database might already be initialized" + fi + + # Create initialization SQL if schema.sql doesn't exist + if [ ! -f "database/schema.sql" ]; then + echo "Creating database schema..." + cat > /tmp/init_db.sql << 'SQLEOF' +-- SilverPAY Database Schema +CREATE TABLE IF NOT EXISTS orders ( + id TEXT PRIMARY KEY, + external_id TEXT UNIQUE NOT NULL, + amount DECIMAL(20,8) NOT NULL, + currency TEXT NOT NULL, + payment_address TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + expires_at TIMESTAMP NOT NULL, + paid_at TIMESTAMP, + tx_hash TEXT, + confirmations INTEGER DEFAULT 0, + webhook_url TEXT, + metadata TEXT +); + +CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status); +CREATE INDEX IF NOT EXISTS idx_orders_external_id ON orders(external_id); +CREATE INDEX IF NOT EXISTS idx_orders_payment_address ON orders(payment_address); + +CREATE TABLE IF NOT EXISTS wallets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + currency TEXT NOT NULL, + address TEXT UNIQUE NOT NULL, + private_key TEXT NOT NULL, + derivation_path TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_used_at TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_wallets_currency ON wallets(currency); +CREATE INDEX IF NOT EXISTS idx_wallets_address ON wallets(address); + +CREATE TABLE IF NOT EXISTS exchange_rates ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + from_currency TEXT NOT NULL, + to_currency TEXT NOT NULL, + rate DECIMAL(20,8) NOT NULL, + source TEXT NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_exchange_rates ON exchange_rates(from_currency, to_currency); +SQLEOF + sqlite3 data/silverpay.db < /tmp/init_db.sql + echo "Database schema created" + fi + + echo -e "\n5. Restarting SilverPAY container..." + + # Stop existing container + if [ ! -z "$CONTAINER_ID" ]; then + docker stop $CONTAINER_ID + docker rm $CONTAINER_ID + fi + + # Check if we have docker-compose + if [ -f "docker-compose.yml" ]; then + echo "Starting with docker-compose..." + docker-compose up -d + else + echo "Starting with docker run..." + # Run SilverPAY container + docker run -d \ + --name silverpay \ + --restart unless-stopped \ + -p 8001:8001 \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/.env:/app/.env \ + -e PYTHONUNBUFFERED=1 \ + silverpay:latest \ + || echo "Container might already exist" + fi + + # Wait for container to start + sleep 5 + + echo -e "\n6. Checking new container status..." + docker ps | grep silverpay + + echo -e "\n7. Testing health endpoint..." + curl -s http://localhost:8001/health || echo "Health check failed" + + echo -e "\n8. Testing order creation..." + curl -X POST http://localhost:8001/api/v1/orders \ + -H "Content-Type: application/json" \ + -H "X-API-Key: sp_live_key_2025_production" \ + -d '{ + "external_id": "test-'$(date +%s)'", + "fiat_amount": 10.00, + "fiat_currency": "GBP", + "currency": "BTC", + "webhook_url": "http://localhost:8080/api/orders/payments/webhook", + "expires_in_hours": 24 + }' 2>/dev/null | python3 -m json.tool || echo "Order creation test failed" + +else + echo "SilverPAY directory not found at /root/silverpay" + echo "Checking alternative location at /opt/silverpay..." + + if [ -d "/opt/silverpay" ]; then + cd /opt/silverpay + echo "Found at /opt/silverpay" + else + echo "ERROR: Cannot find SilverPAY installation!" + echo "You may need to deploy SilverPAY first" + fi +fi + +echo -e "\n=========================================" +echo " FIX COMPLETE - CHECK OUTPUT ABOVE" +echo "=========================================" +EOF + +# Run the fix script on the server +echo "Connecting to server and running fix script..." +sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no root@$SERVER -p $PORT 'bash -s' < /tmp/fix_silverpay.sh + +echo -e "\nāœ… Fix script execution complete!" +echo "Check the output above for any errors." \ No newline at end of file