From 3b4d06a1e5cebe1a159e595b736f994506e7f565 Mon Sep 17 00:00:00 2001 From: Wouter Samaey Date: Thu, 3 Mar 2022 15:15:10 +0100 Subject: [PATCH] Bugfix: Could not find an order by OrderId after it's OrderId was changed through the API --- BTCPayServer.Tests/GreenfieldAPITests.cs | 22 ++++++++++++--- .../Services/Invoices/InvoiceRepository.cs | 27 ++++++++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index f77bdad56..13f90754e 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -1193,12 +1193,13 @@ namespace BTCPayServer.Tests new CreateInvoiceRequest() { Currency = "helloinvalid", Amount = 1 }); }); await user.RegisterDerivationSchemeAsync("BTC"); + string origOrderId = "testOrder"; var newInvoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Currency = "USD", Amount = 1, - Metadata = JObject.Parse("{\"itemCode\": \"testitem\", \"orderId\": \"testOrder\"}"), + Metadata = JObject.Parse($"{{\"itemCode\": \"testitem\", \"orderId\": \"{origOrderId}\"}}"), Checkout = new CreateInvoiceRequest.CheckoutOptions() { RedirectAutomatically = true, @@ -1319,6 +1320,8 @@ namespace BTCPayServer.Tests newInvoice = await client.GetInvoice(user.StoreId, newInvoice.Id); + const string newOrderId = "UPDATED-ORDER-ID"; + JObject metadataForUpdate = JObject.Parse($"{{\"orderId\": \"{newOrderId}\", \"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}}"); Assert.Contains(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking); Assert.DoesNotContain(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking); await AssertHttpError(403, async () => @@ -1326,23 +1329,36 @@ namespace BTCPayServer.Tests await viewOnly.UpdateInvoice(user.StoreId, invoice.Id, new UpdateInvoiceRequest() { - Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}") + Metadata = metadataForUpdate }); }); invoice = await client.UpdateInvoice(user.StoreId, invoice.Id, new UpdateInvoiceRequest() { - Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}") + Metadata = metadataForUpdate }); + Assert.Equal(newOrderId, invoice.Metadata["orderId"].Value()); Assert.Equal("updated", invoice.Metadata["itemCode"].Value()); Assert.Equal(15, ((JArray)invoice.Metadata["newstuff"]).Values().Sum()); //also test the the metadata actually got saved invoice = await client.GetInvoice(user.StoreId, invoice.Id); + Assert.Equal(newOrderId, invoice.Metadata["orderId"].Value()); Assert.Equal("updated", invoice.Metadata["itemCode"].Value()); Assert.Equal(15, ((JArray)invoice.Metadata["newstuff"]).Values().Sum()); + // test if we can find the updated invoice using the new orderId + var invoicesWithOrderId = await client.GetInvoices(user.StoreId, new[] { newOrderId }); + Assert.NotNull(invoicesWithOrderId); + Assert.Single(invoicesWithOrderId); + Assert.Equal(invoice.Id, invoicesWithOrderId.First().Id); + + // test if the old orderId does not yield any results anymore + var invoicesWithOldOrderId = await client.GetInvoices(user.StoreId, new[] { origOrderId }); + Assert.NotNull(invoicesWithOldOrderId); + Assert.Empty(invoicesWithOldOrderId); + //archive await AssertHttpError(403, async () => { diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index f09d22e00..9f9bcea42 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -400,6 +400,14 @@ namespace BTCPayServer.Services.Invoices context.AddRange(filteredTerms); } + public static void RemoveFromTextSearch(ApplicationDbContext context, InvoiceData invoice, + string term) + { + var query = context.InvoiceSearches.AsQueryable(); + var filteredQuery = query.Where( st => st.InvoiceDataId.Equals(invoice.Id) && st.Value.Equals(term)); + context.InvoiceSearches.RemoveRange(filteredQuery); + } + public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState) { using var context = _applicationDbContextFactory.CreateContext(); @@ -462,7 +470,24 @@ namespace BTCPayServer.Services.Invoices StringComparison.InvariantCultureIgnoreCase))) return null; var blob = invoiceData.GetBlob(_btcPayNetworkProvider); - blob.Metadata = InvoiceMetadata.FromJObject(metadata); + + var newMetadata = InvoiceMetadata.FromJObject(metadata); + var oldOrderId = blob.Metadata.OrderId; + var newOrderId = newMetadata.OrderId; + + if (newOrderId != null) + { + // OrderId is saved in 2 places: (1) the invoice table and (2) in the metadata field. We are updating both for consistency. + invoiceData.OrderId = newOrderId; + + if (oldOrderId != null && !newOrderId.Equals(oldOrderId, StringComparison.InvariantCulture)) + { + RemoveFromTextSearch(context, invoiceData, oldOrderId); + } + AddToTextSearch(context, invoiceData, new[] { newOrderId }); + } + + blob.Metadata = newMetadata; invoiceData.Blob = ToBytes(blob); await context.SaveChangesAsync().ConfigureAwait(false); return ToEntity(invoiceData);