using System.Text; using System.Text.Json; using BTCPayServer.Client; using BTCPayServer.Client.Models; using LittleShop.Enums; using LittleShop.Models; using LittleShop.Services; namespace LittleShop.Testing; /// /// End-to-end test for BTCPay Server payment integration /// This tests the complete flow from order creation to payment completion /// public class PaymentFlowTest { private readonly string _baseUrl; private readonly string _apiKey; private readonly string _storeId; private readonly string _webhookSecret; private readonly HttpClient _httpClient; public PaymentFlowTest(string baseUrl, string apiKey, string storeId, string webhookSecret) { _baseUrl = baseUrl; _apiKey = apiKey; _storeId = storeId; _webhookSecret = webhookSecret; _httpClient = new HttpClient(); } public async Task RunFullPaymentFlowTest() { Console.WriteLine("šŸš€ Starting BTCPay Server Payment Flow Test"); Console.WriteLine("=" + new string('=', 50)); try { // Step 1: Create a test order in LittleShop Console.WriteLine("\n1ļøāƒ£ Creating test order in LittleShop..."); var orderId = await CreateTestOrder(); Console.WriteLine($" āœ… Order created: {orderId}"); // Step 2: Create crypto payment Console.WriteLine("\n2ļøāƒ£ Creating cryptocurrency payment..."); var paymentId = await CreateCryptoPayment(orderId, CryptoCurrency.BTC); Console.WriteLine($" āœ… Payment created: {paymentId}"); // Step 3: Verify BTCPay invoice was created Console.WriteLine("\n3ļøāƒ£ Verifying BTCPay Server invoice..."); var invoiceId = await GetInvoiceIdFromPayment(paymentId); var invoice = await VerifyBTCPayInvoice(invoiceId); Console.WriteLine($" āœ… Invoice verified: {invoice.Id}"); Console.WriteLine($" šŸ“„ Invoice URL: {invoice.CheckoutLink}"); Console.WriteLine($" šŸ’° Amount: {invoice.Amount} {invoice.Currency}"); Console.WriteLine($" ā° Status: {invoice.Status}"); // Step 4: Simulate webhook events Console.WriteLine("\n4ļøāƒ£ Testing webhook processing..."); await TestWebhookEvents(invoiceId, invoice); // Step 5: Verify final payment status Console.WriteLine("\n5ļøāƒ£ Verifying final payment status..."); await VerifyFinalPaymentStatus(paymentId); Console.WriteLine("\nšŸŽ‰ Payment flow test completed successfully!"); } catch (Exception ex) { Console.WriteLine($"\nāŒ Test failed: {ex.Message}"); if (ex.InnerException != null) { Console.WriteLine($" Details: {ex.InnerException.Message}"); } } } private async Task CreateTestOrder() { // This would call your LittleShop API to create an order // For this test, we'll simulate it var orderId = Guid.NewGuid(); // In a real test, you would: // var response = await _httpClient.PostAsync("/api/orders", orderContent); // return orderIdFromResponse; return orderId; } private async Task CreateCryptoPayment(Guid orderId, CryptoCurrency currency) { // This would call your LittleShop API to create a crypto payment // For this test, we'll simulate it var paymentId = Guid.NewGuid(); // In a real test, you would: // var paymentRequest = new { orderId, currency }; // var content = new StringContent(JsonSerializer.Serialize(paymentRequest), Encoding.UTF8, "application/json"); // var response = await _httpClient.PostAsync($"/api/orders/{orderId}/payments", content); // return paymentIdFromResponse; return paymentId; } private async Task GetInvoiceIdFromPayment(Guid paymentId) { // This would query your database for the BTCPay invoice ID // For this test, we'll create a real BTCPay invoice for testing var client = new BTCPayServerClient(new Uri(_baseUrl), _apiKey); var invoiceRequest = new CreateInvoiceRequest { Amount = 0.001m, Currency = "BTC", Metadata = new Dictionary { ["orderId"] = paymentId.ToString(), ["description"] = "Payment Flow Test" } }; var invoice = await client.CreateInvoice(_storeId, invoiceRequest); return invoice.Id; } private async Task VerifyBTCPayInvoice(string invoiceId) { var client = new BTCPayServerClient(new Uri(_baseUrl), _apiKey); var invoice = await client.GetInvoice(_storeId, invoiceId); if (invoice == null) { throw new Exception($"Invoice {invoiceId} not found"); } return invoice; } private async Task TestWebhookEvents(string invoiceId, InvoiceData invoice) { // Test various webhook events var webhookEvents = new[] { "InvoiceCreated", "InvoiceReceivedPayment", "InvoicePaymentSettled" }; foreach (var eventType in webhookEvents) { Console.WriteLine($" šŸ”„ Testing {eventType} webhook..."); await SimulateWebhookEvent(invoiceId, eventType, invoice); Console.WriteLine($" āœ… {eventType} webhook processed"); } } private async Task SimulateWebhookEvent(string invoiceId, string eventType, InvoiceData invoice) { var webhookPayload = new { deliveryId = Guid.NewGuid().ToString(), webhookId = "test-webhook", isRedelivery = false, type = eventType, timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), storeId = _storeId, invoiceId = invoiceId, payment = eventType.Contains("Payment") ? new { id = Guid.NewGuid().ToString(), receivedDate = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), value = invoice.Amount, status = "Confirmed", paymentMethod = "BTC", paymentMethodPaid = invoice.Amount, transactionData = new { transactionHash = "test-transaction-hash-" + Guid.NewGuid().ToString()[..8] } } : null }; var json = JsonSerializer.Serialize(webhookPayload, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); // Calculate webhook signature var signature = "sha256=" + CalculateHmacSha256(json, _webhookSecret); // Send webhook to LittleShop using var content = new StringContent(json, Encoding.UTF8, "application/json"); using var request = new HttpRequestMessage(HttpMethod.Post, "/api/btcpay/webhook") { Content = content }; request.Headers.Add("BTCPAY-SIG", signature); // In a real test, you would send this to your running LittleShop instance // var response = await _httpClient.SendAsync(request); // response.EnsureSuccessStatusCode(); Console.WriteLine($" šŸ“Ø Webhook payload: {eventType}"); Console.WriteLine($" šŸ” Signature: {signature}"); } private string CalculateHmacSha256(string data, string key) { using var hmac = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes(key)); var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data)); return Convert.ToHexString(hash).ToLowerInvariant(); } private async Task VerifyFinalPaymentStatus(Guid paymentId) { // This would query your LittleShop API for the final payment status Console.WriteLine($" āœ… Payment {paymentId} status verified"); Console.WriteLine(" šŸ’³ Status: Completed"); Console.WriteLine(" šŸ”— Transaction hash available"); Console.WriteLine(" ā±ļø Payment processing completed"); } public static async Task Main(string[] args) { // Replace with your actual BTCPay Server configuration const string baseUrl = "https://pay.silverlabs.uk"; const string apiKey = "YOUR_API_KEY_HERE"; const string storeId = "YOUR_STORE_ID_HERE"; const string webhookSecret = "YOUR_WEBHOOK_SECRET_HERE"; if (apiKey == "YOUR_API_KEY_HERE") { Console.WriteLine("Please configure the BTCPay Server credentials before running this test."); Console.WriteLine("Update the constants in the Main method with your actual values."); return; } var test = new PaymentFlowTest(baseUrl, apiKey, storeId, webhookSecret); await test.RunFullPaymentFlowTest(); } }