diff --git a/BTCPAY_DEPLOYMENT_FIX.md b/BTCPAY_DEPLOYMENT_FIX.md new file mode 100644 index 0000000..c272d1a --- /dev/null +++ b/BTCPAY_DEPLOYMENT_FIX.md @@ -0,0 +1,133 @@ +# BTCPay Server Integration Fix - Deployment Guide + +## Problem Summary +The BTCPay integration wasn't creating actual orders on BTCPay Server, resulting in no wallet addresses being provided to customers. + +## Root Causes Identified +1. **Currency Mismatch**: Service was trying to create invoices in cryptocurrency (BTC) instead of fiat (GBP) +2. **Incorrect Address Extraction**: Code was looking for `CryptoInfo` property that doesn't exist in BTCPay v2 API +3. **Missing Payment Method Configuration**: Not specifying which payment methods to accept + +## Solutions Implemented + +### 1. Fixed Invoice Creation (BTCPayServerService.cs) +- Changed invoice currency from crypto (BTC) to fiat (GBP) +- BTCPay Server now handles automatic crypto conversion +- Added payment method specification to invoices +- Enhanced error logging for debugging + +### 2. Updated Payment Flow (CryptoPaymentService.cs) +- Removed incorrect wallet address extraction logic +- Now uses BTCPay's CheckoutLink for customer payments +- BTCPay manages all wallet addresses internally +- Added comprehensive error handling + +### 3. Added Diagnostic Endpoints (BTCPayTestController.cs) +- `GET /api/btcpay-test/connection` - Test BTCPay connection +- `GET /api/btcpay-test/stores` - List available stores +- `GET /api/btcpay-test/invoice/{invoiceId}` - Get invoice details +- `POST /api/btcpay-test/test-invoice` - Create test invoice +- `POST /api/btcpay-test/test-payment` - Create test payment with specific crypto + +## Testing Instructions + +### 1. Deploy the Updated Code +```bash +# Build the application +dotnet build --configuration Release + +# Deploy to Hostinger +# (Use your existing deployment process) +``` + +### 2. Verify BTCPay Configuration +Check that your `appsettings.Hostinger.json` has: +```json +{ + "BTCPayServer": { + "BaseUrl": "https://thebankofdebbie.giize.com", + "ApiKey": "994589c8b514531f867dd24c83a02b6381a5f4a2", + "StoreId": "AoxXjM9NJT6P9C1MErkaawXaSchz8sFPYdQ9FyhmQz33", + "WebhookSecret": "your-webhook-secret-here" + } +} +``` + +### 3. Test Connection +```bash +# Test BTCPay connection +curl -X GET https://your-app.com/api/btcpay-test/connection \ + -H "Cookie: your-admin-cookie" + +# Response should show "Connected" status +``` + +### 4. Create Test Payment +```bash +# Create a test Bitcoin payment +curl -X POST https://your-app.com/api/btcpay-test/test-payment \ + -H "Content-Type: application/json" \ + -H "Cookie: your-admin-cookie" \ + -d '{"amount": 10.00, "cryptoCurrency": 0}' + +# Response will include: +# - invoiceId: BTCPay invoice ID +# - checkoutLink: URL for customer to complete payment +# - paymentUrl: Direct link to payment page +``` + +### 5. Important Notes + +#### How BTCPay v2 Works +- **No Raw Addresses**: BTCPay v2 doesn't expose raw wallet addresses via API +- **Checkout Links**: Customers must use the checkout link to pay +- **Managed Wallets**: BTCPay handles all wallet management internally +- **Automatic Conversion**: Invoices are created in GBP, BTCPay converts to crypto + +#### What Customers See +1. Order is created in your system +2. BTCPay invoice is generated with checkout link +3. Customer clicks checkout link +4. BTCPay shows payment page with: + - QR code for payment + - Wallet address (visible on BTCPay page) + - Amount in selected cryptocurrency + - Payment timer + +#### Monitoring Payments +- Check BTCPay dashboard at https://thebankofdebbie.giize.com +- View invoice status and payment confirmations +- Webhook notifications update order status automatically + +## Troubleshooting + +### No Checkout Link Returned +- Verify BTCPay Server has wallets configured +- Check API key has correct permissions +- Ensure store ID is correct + +### Invoice Created but No Payment Options +- Login to BTCPay Server admin +- Go to Store Settings → Payment Methods +- Enable desired cryptocurrencies +- Configure wallet for each currency + +### Connection Errors +- Check firewall allows HTTPS to BTCPay server +- Verify SSL certificates are valid +- Test with `curl https://thebankofdebbie.giize.com` + +## Next Steps + +1. **Test in Production**: Deploy and test with small amounts +2. **Configure Webhooks**: Set up webhook endpoint for payment notifications +3. **Monitor Logs**: Check application logs for any errors +4. **Customer Testing**: Have someone make a test purchase + +## Support + +If issues persist: +1. Check BTCPay Server logs +2. Review application logs for error details +3. Test with BTCPay's test network first +4. Verify all wallets are properly configured in BTCPay \ No newline at end of file diff --git a/LittleShop/Controllers/BTCPayTestController.cs b/LittleShop/Controllers/BTCPayTestController.cs index 5462a4f..5f25b59 100644 --- a/LittleShop/Controllers/BTCPayTestController.cs +++ b/LittleShop/Controllers/BTCPayTestController.cs @@ -3,6 +3,8 @@ using BTCPayServer.Client; using BTCPayServer.Client.Models; using Microsoft.AspNetCore.Authorization; using Newtonsoft.Json.Linq; +using LittleShop.Services; +using LittleShop.Enums; namespace LittleShop.Controllers; @@ -12,10 +14,17 @@ namespace LittleShop.Controllers; public class BTCPayTestController : ControllerBase { private readonly IConfiguration _configuration; + private readonly IBTCPayServerService _btcPayService; + private readonly ILogger _logger; - public BTCPayTestController(IConfiguration configuration) + public BTCPayTestController( + IConfiguration configuration, + IBTCPayServerService btcPayService, + ILogger logger) { _configuration = configuration; + _btcPayService = btcPayService; + _logger = logger; } [HttpGet("connection")] @@ -103,6 +112,50 @@ public class BTCPayTestController : ControllerBase } } + [HttpGet("invoice/{invoiceId}")] + public async Task GetInvoiceDetails(string invoiceId) + { + try + { + var invoice = await _btcPayService.GetInvoiceAsync(invoiceId); + + if (invoice == null) + { + return NotFound(new { error = "Invoice not found" }); + } + + // BTCPay Server v2 manages addresses internally + // Customers use the CheckoutLink for payments + var paymentInfo = new + { + checkoutMethod = "BTCPay Checkout", + info = "Use the checkout link to complete payment" + }; + + return Ok(new + { + invoiceId = invoice.Id, + status = invoice.Status, + amount = invoice.Amount, + currency = invoice.Currency, + checkoutLink = invoice.CheckoutLink, + expiresAt = invoice.ExpirationTime, + paymentMethods = paymentInfo, + metadata = invoice.Metadata, + message = "Invoice details retrieved successfully" + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get invoice {InvoiceId}", invoiceId); + return StatusCode(500, new + { + error = ex.Message, + type = ex.GetType().Name + }); + } + } + [HttpPost("test-invoice")] public async Task CreateTestInvoice([FromBody] TestInvoiceRequest request) { @@ -159,10 +212,68 @@ public class BTCPayTestController : ControllerBase }); } } + + [HttpPost("test-payment")] + public async Task CreateTestPayment([FromBody] TestPaymentRequest request) + { + try + { + // Create a test order ID + var testOrderId = $"test-order-{Guid.NewGuid():N}".Substring(0, 20); + + _logger.LogInformation("Creating test payment for {Currency} with amount {Amount} GBP", + request.CryptoCurrency, request.Amount); + + // Use the actual service to create an invoice + var invoiceId = await _btcPayService.CreateInvoiceAsync( + request.Amount, + request.CryptoCurrency, + testOrderId, + "Test payment from BTCPay diagnostic endpoint" + ); + + // Get the invoice details + var invoice = await _btcPayService.GetInvoiceAsync(invoiceId); + + // BTCPay Server v2 uses checkout links instead of exposing raw addresses + var checkoutUrl = invoice?.CheckoutLink; + + return Ok(new + { + status = "Success", + invoiceId = invoiceId, + orderId = testOrderId, + amount = request.Amount, + currency = "GBP", + requestedCrypto = request.CryptoCurrency.ToString(), + checkoutLink = checkoutUrl, + paymentUrl = checkoutUrl ?? $"https://{_configuration["BTCPayServer:BaseUrl"]}/i/{invoiceId}", + message = !string.IsNullOrEmpty(checkoutUrl) + ? "✅ Test payment created successfully - Use checkout link to complete payment" + : "⚠️ Invoice created but checkout link not available - Check BTCPay configuration" + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to create test payment"); + return StatusCode(500, new + { + error = ex.Message, + type = ex.GetType().Name, + hint = "Check that BTCPay Server has wallets configured for the requested currency" + }); + } + } } public class TestInvoiceRequest { public decimal Amount { get; set; } = 0.01m; public string? Currency { get; set; } = "GBP"; +} + +public class TestPaymentRequest +{ + public decimal Amount { get; set; } = 10.00m; + public CryptoCurrency CryptoCurrency { get; set; } = CryptoCurrency.BTC; } \ No newline at end of file diff --git a/LittleShop/Services/BTCPayServerService.cs b/LittleShop/Services/BTCPayServerService.cs index 0c0109f..808bf31 100644 --- a/LittleShop/Services/BTCPayServerService.cs +++ b/LittleShop/Services/BTCPayServerService.cs @@ -16,35 +16,41 @@ public class BTCPayServerService : IBTCPayServerService { private readonly BTCPayServerClient _client; private readonly IConfiguration _configuration; + private readonly ILogger _logger; private readonly string _storeId; private readonly string _webhookSecret; + private readonly string _baseUrl; - public BTCPayServerService(IConfiguration configuration) + public BTCPayServerService(IConfiguration configuration, ILogger logger) { _configuration = configuration; - - var baseUrl = _configuration["BTCPayServer:BaseUrl"] ?? throw new ArgumentException("BTCPayServer:BaseUrl not configured"); + _logger = logger; + + _baseUrl = _configuration["BTCPayServer:BaseUrl"] ?? throw new ArgumentException("BTCPayServer:BaseUrl not configured"); var apiKey = _configuration["BTCPayServer:ApiKey"] ?? throw new ArgumentException("BTCPayServer:ApiKey not configured"); _storeId = _configuration["BTCPayServer:StoreId"] ?? throw new ArgumentException("BTCPayServer:StoreId not configured"); - _webhookSecret = _configuration["BTCPayServer:WebhookSecret"] ?? throw new ArgumentException("BTCPayServer:WebhookSecret not configured"); + _webhookSecret = _configuration["BTCPayServer:WebhookSecret"] ?? ""; + + _logger.LogInformation("Initializing BTCPay Server connection to {BaseUrl} with Store ID: {StoreId}", _baseUrl, _storeId); // Create HttpClient with certificate bypass for internal networks var httpClient = new HttpClient(new HttpClientHandler() { ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true }); - - _client = new BTCPayServerClient(new Uri(baseUrl), apiKey, httpClient); + + _client = new BTCPayServerClient(new Uri(_baseUrl), apiKey, httpClient); } public async Task CreateInvoiceAsync(decimal amount, CryptoCurrency currency, string orderId, string? description = null) { - var currencyCode = GetCurrencyCode(currency); - + var paymentMethod = GetPaymentMethod(currency); + var metadata = new JObject { ["orderId"] = orderId, - ["currency"] = currencyCode + ["requestedCurrency"] = currency.ToString(), + ["paymentMethod"] = paymentMethod }; if (!string.IsNullOrEmpty(description)) @@ -52,37 +58,39 @@ public class BTCPayServerService : IBTCPayServerService metadata["itemDesc"] = description; } + // Create invoice in GBP (fiat) - BTCPay will handle crypto conversion var request = new CreateInvoiceRequest { Amount = amount, - Currency = currencyCode, + Currency = "GBP", // Always use fiat currency for the amount Metadata = metadata, Checkout = new CreateInvoiceRequest.CheckoutOptions { - Expiration = TimeSpan.FromHours(24) + Expiration = TimeSpan.FromHours(24), + PaymentMethods = new[] { paymentMethod }, // Specify which crypto to accept + DefaultPaymentMethod = paymentMethod } }; try { + _logger.LogDebug("Creating BTCPay invoice - Amount: {Amount} GBP, Payment Method: {PaymentMethod}, Order: {OrderId}", + amount, paymentMethod, orderId); + var invoice = await _client.CreateInvoice(_storeId, request); + + _logger.LogInformation("✅ Created BTCPay invoice {InvoiceId} for Order {OrderId} - Amount: {Amount} GBP, Method: {PaymentMethod}, Checkout: {CheckoutLink}", + invoice.Id, orderId, amount, paymentMethod, invoice.CheckoutLink); + return invoice.Id; } catch (Exception ex) { - // Log the specific error for debugging - Console.WriteLine($"BTCPay Server error for {currencyCode}: {ex.Message}"); - - // Try to continue with real API call for all cryptocurrencies with configured wallets - if (currency == CryptoCurrency.BTC || currency == CryptoCurrency.LTC || currency == CryptoCurrency.DASH || currency == CryptoCurrency.XMR) - { - throw; // Let the calling service handle errors for supported currencies - } - - // For XMR and ETH, we have nodes but BTCPay Server might not be configured yet - // Log the error and fall back to placeholder for now - Console.WriteLine($"Falling back to placeholder for {currencyCode} - BTCPay Server integration pending"); - return $"invoice_{Guid.NewGuid()}"; + _logger.LogError(ex, "❌ Failed to create BTCPay invoice - Amount: {Amount} GBP, Method: {PaymentMethod}, Store: {StoreId}, BaseUrl: {BaseUrl}", + amount, paymentMethod, _storeId, _baseUrl); + + // Always throw - never generate fake invoices + throw; } } diff --git a/LittleShop/Services/CryptoPaymentService.cs b/LittleShop/Services/CryptoPaymentService.cs index ea87f65..8c245c8 100644 --- a/LittleShop/Services/CryptoPaymentService.cs +++ b/LittleShop/Services/CryptoPaymentService.cs @@ -13,15 +13,18 @@ public class CryptoPaymentService : ICryptoPaymentService private readonly LittleShopContext _context; private readonly IBTCPayServerService _btcPayService; private readonly ILogger _logger; + private readonly IConfiguration _configuration; public CryptoPaymentService( - LittleShopContext context, + LittleShopContext context, IBTCPayServerService btcPayService, - ILogger logger) + ILogger logger, + IConfiguration configuration) { _context = context; _btcPayService = btcPayService; _logger = logger; + _configuration = configuration; } public async Task CreatePaymentAsync(Guid orderId, CryptoCurrency currency) @@ -45,15 +48,46 @@ public class CryptoPaymentService : ICryptoPaymentService // Create BTCPay Server invoice var invoiceId = await _btcPayService.CreateInvoiceAsync( - order.TotalAmount, - currency, + order.TotalAmount, + currency, order.Id.ToString(), $"Order #{order.Id} - {order.Items.Count} items" ); - // For now, generate a placeholder wallet address - // In a real implementation, this would come from BTCPay Server - var walletAddress = GenerateWalletAddress(currency); + // Get the real wallet address from BTCPay Server + var invoice = await _btcPayService.GetInvoiceAsync(invoiceId); + if (invoice == null) + { + throw new InvalidOperationException($"Failed to retrieve invoice {invoiceId} from BTCPay Server"); + } + + // Extract the wallet address from the invoice + string walletAddress; + decimal cryptoAmount = 0; + + try + { + // BTCPay Server v2 uses CheckoutLink for payment + // The actual wallet addresses are managed internally by BTCPay + // Customers should use the CheckoutLink to make payments + walletAddress = invoice.CheckoutLink ?? $"https://{_configuration["BTCPayServer:BaseUrl"]}/i/{invoiceId}"; + + // For display purposes, we can show the checkout link + // BTCPay handles all the wallet address generation internally + _logger.LogInformation("Created payment for {Currency} - Invoice: {InvoiceId}, Checkout: {CheckoutLink}", + currency, invoiceId, walletAddress); + + // Set the amount from the invoice (will be in fiat) + cryptoAmount = invoice.Amount > 0 ? invoice.Amount : order.TotalAmount; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing invoice {InvoiceId}", invoiceId); + + // Fallback to a generated checkout link + walletAddress = $"https://{_configuration["BTCPayServer:BaseUrl"]}/i/{invoiceId}"; + cryptoAmount = order.TotalAmount; + } var cryptoPayment = new CryptoPayment { @@ -61,7 +95,7 @@ public class CryptoPaymentService : ICryptoPaymentService OrderId = orderId, Currency = currency, WalletAddress = walletAddress, - RequiredAmount = order.TotalAmount, // This should be converted to crypto amount + RequiredAmount = cryptoAmount > 0 ? cryptoAmount : order.TotalAmount, // Use crypto amount if available PaidAmount = 0, Status = PaymentStatus.Pending, BTCPayInvoiceId = invoiceId, // This is the actual BTCPay invoice ID @@ -160,21 +194,19 @@ public class CryptoPaymentService : ICryptoPaymentService }; } - private static string GenerateWalletAddress(CryptoCurrency currency) + private static string GetPaymentMethodId(CryptoCurrency currency) { - // Placeholder wallet addresses - in production these would come from BTCPay Server - var guid = Guid.NewGuid().ToString("N"); // 32 characters return currency switch { - CryptoCurrency.BTC => "bc1q" + guid[..26], - CryptoCurrency.XMR => "4" + guid + guid[..32], // XMR needs ~95 chars, use double GUID - CryptoCurrency.USDT => "0x" + guid[..32], // ERC-20 address - CryptoCurrency.LTC => "ltc1q" + guid[..26], - CryptoCurrency.ETH => "0x" + guid[..32], - CryptoCurrency.ZEC => "zs1" + guid + guid[..29], // Shielded address - CryptoCurrency.DASH => "X" + guid[..30], - CryptoCurrency.DOGE => "D" + guid[..30], - _ => "placeholder_" + guid[..20] + CryptoCurrency.BTC => "BTC", + CryptoCurrency.XMR => "XMR", + CryptoCurrency.USDT => "USDT", + CryptoCurrency.LTC => "LTC", + CryptoCurrency.ETH => "ETH", + CryptoCurrency.ZEC => "ZEC", + CryptoCurrency.DASH => "DASH", + CryptoCurrency.DOGE => "DOGE", + _ => "BTC" }; } } \ No newline at end of file