diff --git a/BTCPayServer.Data/BTCPayServer.Data.csproj b/BTCPayServer.Data/BTCPayServer.Data.csproj index fdece06d5..f2042776f 100644 --- a/BTCPayServer.Data/BTCPayServer.Data.csproj +++ b/BTCPayServer.Data/BTCPayServer.Data.csproj @@ -23,5 +23,6 @@ + diff --git a/BTCPayServer.Data/DBScripts/006.PaymentsRenaming.sql b/BTCPayServer.Data/DBScripts/006.PaymentsRenaming.sql new file mode 100644 index 000000000..393b41180 --- /dev/null +++ b/BTCPayServer.Data/DBScripts/006.PaymentsRenaming.sql @@ -0,0 +1,18 @@ +DROP FUNCTION get_monitored_invoices; +CREATE OR REPLACE FUNCTION get_monitored_invoices(payment_method_id TEXT, include_non_activated BOOLEAN) +RETURNS TABLE (invoice_id TEXT, payment_id TEXT, payment_method_id TEXT) AS $$ +WITH cte AS ( +-- Get all the invoices which are pending. Even if no payments. +SELECT i."Id" invoice_id, p."Id" payment_id, p."PaymentMethodId" payment_method_id FROM "Invoices" i LEFT JOIN "Payments" p ON i."Id" = p."InvoiceDataId" + WHERE is_pending(i."Status") +UNION ALL +-- For invoices not pending, take all of those which have pending payments +SELECT i."Id", p."Id", p."PaymentMethodId" payment_method_id FROM "Invoices" i INNER JOIN "Payments" p ON i."Id" = p."InvoiceDataId" + WHERE is_pending(p."Status") AND NOT is_pending(i."Status")) +SELECT cte.* FROM cte +LEFT JOIN "Payments" p ON cte.payment_id=p."Id" AND cte.payment_id=p."PaymentMethodId" +LEFT JOIN "Invoices" i ON cte.invoice_id=i."Id" +WHERE (p."PaymentMethodId" IS NOT NULL AND p."PaymentMethodId" = payment_method_id) OR + (p."PaymentMethodId" IS NULL AND get_prompt(i."Blob2", payment_method_id) IS NOT NULL AND + (include_non_activated IS TRUE OR (get_prompt(i."Blob2", payment_method_id)->'activated')::BOOLEAN IS NOT FALSE)); +$$ LANGUAGE SQL STABLE; diff --git a/BTCPayServer.Data/Migrations/20240924071444_temprefactor3.cs b/BTCPayServer.Data/Migrations/20240924071444_temprefactor3.cs new file mode 100644 index 000000000..6884ded7d --- /dev/null +++ b/BTCPayServer.Data/Migrations/20240924071444_temprefactor3.cs @@ -0,0 +1,15 @@ +using BTCPayServer.Data; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BTCPayServer.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240924071444_temprefactor3")] + [DBScript("006.PaymentsRenaming.sql")] + public partial class temprefactor3 : DBScriptsMigration + { + } +} diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index d7ae0e270..c654c2901 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -14,6 +14,7 @@ using BTCPayServer.Events; using BTCPayServer.Lightning; using BTCPayServer.Models.InvoicingModels; using BTCPayServer.NTag424; +using BTCPayServer.Payments; using BTCPayServer.Payments.Lightning; using BTCPayServer.PayoutProcessors; using BTCPayServer.Plugins.PointOfSale.Controllers; @@ -2711,6 +2712,14 @@ namespace BTCPayServer.Tests invoiceObject = await client.GetOnChainWalletObject(user.StoreId, "BTC", new OnChainWalletObjectId("invoice", invoice.Id), false); Assert.DoesNotContain(invoiceObject.Links.Select(l => l.Type), t => t == "address"); + // Check if we can get the monitored invoice + var invoiceRepo = tester.PayTester.GetService(); + var includeNonActivated = true; + Assert.Single(await invoiceRepo.GetMonitoredInvoices(PaymentMethodId.Parse("BTC-CHAIN"), includeNonActivated), i => i.Id == invoice.Id); + includeNonActivated = false; + Assert.Single(await invoiceRepo.GetMonitoredInvoices(PaymentMethodId.Parse("BTC-CHAIN"), includeNonActivated), i => i.Id == invoice.Id); + Assert.Single(await invoiceRepo.GetMonitoredInvoices(PaymentMethodId.Parse("BTC-CHAIN")), i => i.Id == invoice.Id); + // paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id); Assert.Single(paymentMethods); diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 88271c53d..814d971d1 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -3161,9 +3161,11 @@ namespace BTCPayServer.Tests var invoiceRepo = tester.PayTester.GetService(); var monitored = Assert.Single(await invoiceRepo.GetMonitoredInvoices(PaymentMethodId.Parse("BTC-CHAIN")), i => i.Id == invoiceId); Assert.Single(monitored.Payments); - // + monitored = Assert.Single(await invoiceRepo.GetMonitoredInvoices(PaymentMethodId.Parse("BTC-CHAIN"), true), i => i.Id == invoiceId); + Assert.Single(monitored.Payments); + // - app = await client.CreatePointOfSaleApp(acc.StoreId, new PointOfSaleAppRequest + app = await client.CreatePointOfSaleApp(acc.StoreId, new PointOfSaleAppRequest { AppName = "Cart", DefaultView = PosViewType.Cart, diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index 2088bd977..7c06a1070 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -88,11 +88,30 @@ namespace BTCPayServer.Services.Invoices /// The payment method id /// Cancellation token /// - public async Task GetMonitoredInvoices(PaymentMethodId paymentMethodId, CancellationToken cancellationToken = default) + public Task GetMonitoredInvoices(PaymentMethodId paymentMethodId, CancellationToken cancellationToken = default) + => GetMonitoredInvoices(paymentMethodId, false, cancellationToken: cancellationToken); + + /// + /// Returns all invoices which either: + /// * Have the activated and are pending + /// * Aren't pending but have a payment from the that is pending + /// is filled with the monitored addresses of the for this invoice. + /// include the payments for this invoice. + /// + /// The payment method id + /// If true, include pending invoice with non activated payment methods + /// Cancellation token + /// + public async Task GetMonitoredInvoices(PaymentMethodId paymentMethodId, bool includeNonActivated, CancellationToken cancellationToken = default) { var pmi = paymentMethodId.ToString(); using var ctx = _applicationDbContextFactory.CreateContext(); var conn = ctx.Database.GetDbConnection(); + + string includeNonActivateQuery = String.Empty; + if (includeNonActivated) + includeNonActivateQuery = " AND (get_prompt(i.\"Blob2\", @pmi)->'activated')::BOOLEAN IS NOT FALSE)"; + var rows = await conn.QueryAsync<(string Id, uint xmin, string[] addresses, string[] payments, string invoice)>(new(""" SELECT i."Id", @@ -100,14 +119,14 @@ namespace BTCPayServer.Services.Invoices array_agg(ai."Address") addresses, COALESCE(array_agg(to_jsonb(p)) FILTER (WHERE p."Id" IS NOT NULL), '{}') as payments, (array_agg(to_jsonb(i)))[1] as invoice - FROM get_monitored_invoices(@pmi) m + FROM get_monitored_invoices(@pmi, @includeNonActivated) m LEFT JOIN "Payments" p ON p."Id" = m.payment_id AND p."PaymentMethodId" = m.payment_method_id LEFT JOIN "Invoices" i ON i."Id" = m.invoice_id LEFT JOIN "AddressInvoices" ai ON i."Id" = ai."InvoiceDataId" WHERE ai."PaymentMethodId" = @pmi GROUP BY i."Id"; """ - , new { pmi = paymentMethodId.ToString() })); + , new { pmi = paymentMethodId.ToString(), includeNonActivated })); if (Enumerable.TryGetNonEnumeratedCount(rows, out var c) && c == 0) return Array.Empty(); List invoices = new List();