- Fixed invoice creation to use GBP (fiat) instead of cryptocurrency amounts - BTCPay Server now handles automatic crypto conversion - Updated payment flow to use checkout links instead of raw wallet addresses - Added comprehensive logging for debugging payment issues - Created diagnostic endpoints for testing BTCPay connection and payments - Added documentation for deployment and troubleshooting The key issue was that BTCPay v2 manages wallet addresses internally and provides checkout links for customers to complete payments, rather than exposing raw crypto addresses. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
279 lines
9.4 KiB
C#
279 lines
9.4 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using BTCPayServer.Client;
|
|
using BTCPayServer.Client.Models;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Newtonsoft.Json.Linq;
|
|
using LittleShop.Services;
|
|
using LittleShop.Enums;
|
|
|
|
namespace LittleShop.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/btcpay-test")]
|
|
[Authorize(AuthenticationSchemes = "Cookies", Roles = "Admin")]
|
|
public class BTCPayTestController : ControllerBase
|
|
{
|
|
private readonly IConfiguration _configuration;
|
|
private readonly IBTCPayServerService _btcPayService;
|
|
private readonly ILogger<BTCPayTestController> _logger;
|
|
|
|
public BTCPayTestController(
|
|
IConfiguration configuration,
|
|
IBTCPayServerService btcPayService,
|
|
ILogger<BTCPayTestController> logger)
|
|
{
|
|
_configuration = configuration;
|
|
_btcPayService = btcPayService;
|
|
_logger = logger;
|
|
}
|
|
|
|
[HttpGet("connection")]
|
|
public async Task<IActionResult> TestConnection()
|
|
{
|
|
try
|
|
{
|
|
var baseUrl = _configuration["BTCPayServer:BaseUrl"];
|
|
var apiKey = _configuration["BTCPayServer:ApiKey"];
|
|
|
|
if (string.IsNullOrEmpty(baseUrl) || string.IsNullOrEmpty(apiKey))
|
|
{
|
|
return BadRequest(new { error = "BTCPay Server configuration missing" });
|
|
}
|
|
|
|
// Create HttpClient with certificate bypass for internal networks
|
|
var httpClient = new HttpClient(new HttpClientHandler()
|
|
{
|
|
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
|
|
});
|
|
|
|
var client = new BTCPayServerClient(new Uri(baseUrl), apiKey, httpClient);
|
|
|
|
// Test basic connection by getting server info
|
|
var serverInfo = await client.GetServerInfo();
|
|
|
|
return Ok(new
|
|
{
|
|
status = "Connected",
|
|
baseUrl = baseUrl,
|
|
serverVersion = serverInfo?.Version,
|
|
supportedPaymentMethods = serverInfo?.SupportedPaymentMethods,
|
|
message = "BTCPay Server connection successful"
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new
|
|
{
|
|
error = ex.Message,
|
|
type = ex.GetType().Name,
|
|
baseUrl = _configuration["BTCPayServer:BaseUrl"]
|
|
});
|
|
}
|
|
}
|
|
|
|
[HttpGet("stores")]
|
|
public async Task<IActionResult> GetStores()
|
|
{
|
|
try
|
|
{
|
|
var baseUrl = _configuration["BTCPayServer:BaseUrl"];
|
|
var apiKey = _configuration["BTCPayServer:ApiKey"];
|
|
|
|
// Create HttpClient with certificate bypass for internal networks
|
|
var httpClient = new HttpClient(new HttpClientHandler()
|
|
{
|
|
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
|
|
});
|
|
|
|
var client = new BTCPayServerClient(new Uri(baseUrl), apiKey, httpClient);
|
|
|
|
// Get available stores
|
|
var stores = await client.GetStores();
|
|
|
|
return Ok(new
|
|
{
|
|
stores = stores.Select(s => new
|
|
{
|
|
id = s.Id,
|
|
name = s.Name,
|
|
website = s.Website,
|
|
defaultCurrency = s.DefaultCurrency
|
|
}).ToList(),
|
|
message = "Stores retrieved successfully"
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new
|
|
{
|
|
error = ex.Message,
|
|
type = ex.GetType().Name
|
|
});
|
|
}
|
|
}
|
|
|
|
[HttpGet("invoice/{invoiceId}")]
|
|
public async Task<IActionResult> 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<IActionResult> CreateTestInvoice([FromBody] TestInvoiceRequest request)
|
|
{
|
|
try
|
|
{
|
|
var baseUrl = _configuration["BTCPayServer:BaseUrl"];
|
|
var apiKey = _configuration["BTCPayServer:ApiKey"];
|
|
var storeId = _configuration["BTCPayServer:StoreId"];
|
|
|
|
if (string.IsNullOrEmpty(storeId))
|
|
{
|
|
return BadRequest(new { error = "Store ID not configured" });
|
|
}
|
|
|
|
// Create HttpClient with certificate bypass for internal networks
|
|
var httpClient = new HttpClient(new HttpClientHandler()
|
|
{
|
|
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
|
|
});
|
|
|
|
var client = new BTCPayServerClient(new Uri(baseUrl), apiKey, httpClient);
|
|
|
|
// Create test invoice
|
|
var invoiceRequest = new CreateInvoiceRequest
|
|
{
|
|
Amount = request.Amount,
|
|
Currency = request.Currency ?? "GBP",
|
|
Metadata = JObject.FromObject(new
|
|
{
|
|
orderId = $"test-{Guid.NewGuid()}",
|
|
source = "LittleShop-Test"
|
|
})
|
|
};
|
|
|
|
var invoice = await client.CreateInvoice(storeId, invoiceRequest);
|
|
|
|
return Ok(new
|
|
{
|
|
status = "Invoice Created",
|
|
invoiceId = invoice.Id,
|
|
amount = invoice.Amount,
|
|
currency = invoice.Currency,
|
|
checkoutLink = invoice.CheckoutLink,
|
|
expiresAt = invoice.ExpirationTime,
|
|
message = "Test invoice created successfully"
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new
|
|
{
|
|
error = ex.Message,
|
|
type = ex.GetType().Name
|
|
});
|
|
}
|
|
}
|
|
|
|
[HttpPost("test-payment")]
|
|
public async Task<IActionResult> 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;
|
|
} |