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();
}
}