From 2d38113c6616f2e36b624c6a6badce93486ee756 Mon Sep 17 00:00:00 2001 From: Nicolas Dorier Date: Tue, 12 Sep 2023 16:33:37 +0900 Subject: [PATCH] Remove legacy confusing export (#5293) --- BTCPayServer.Tests/UnitTest1.cs | 142 --------------- .../Controllers/UIInvoiceController.UI.cs | 37 ---- .../Services/Invoices/Export/InvoiceExport.cs | 169 ------------------ .../Views/UIInvoice/ListInvoices.cshtml | 16 +- 4 files changed, 9 insertions(+), 355 deletions(-) delete mode 100644 BTCPayServer/Services/Invoices/Export/InvoiceExport.cs diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 8a0b34256..94858b259 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -1700,109 +1700,6 @@ namespace BTCPayServer.Tests } } - [Fact(Timeout = LongRunningTestTimeout)] - [Trait("Integration", "Integration")] - public async Task CanExportInvoicesJson() - { - decimal GetFieldValue(string input, string fieldName) - { - var match = Regex.Match(input, $"\"{fieldName}\":([^,]*)"); - Assert.True(match.Success); - return decimal.Parse(match.Groups[1].Value.Trim(), CultureInfo.InvariantCulture); - } - - async Task GetExport(TestAccount account, string storeId = null) - { - var content = await account.GetController(false) - .Export("json", storeId); - var result = Assert.IsType(content); - Assert.Equal("application/json", result.ContentType); - return JsonConvert.DeserializeObject(result.Content ?? "[]"); - } - - using var tester = CreateServerTester(); - await tester.StartAsync(); - var user = tester.NewAccount(); - await user.GrantAccessAsync(); - user.RegisterDerivationScheme("BTC"); - await user.SetNetworkFeeMode(NetworkFeeMode.Always); - var invoice = await user.BitPay.CreateInvoiceAsync( - new Invoice - { - Price = 10, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some \", description", - FullNotifications = true - }, Facade.Merchant); - - var networkFee = new FeeRate(invoice.MinerFees["BTC"].SatoshiPerBytes).GetFee(100); - var result = await GetExport(user); - Assert.Single(result); - - var cashCow = tester.ExplorerNode; - var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); - var firstPayment = invoice.CryptoInfo[0].TotalDue - 3 * networkFee; - cashCow.SendToAddress(invoiceAddress, firstPayment); - Thread.Sleep(1000); // prevent race conditions, ordering payments - // look if you can reduce thread sleep, this was min value for me - - // should reduce invoice due by 0 USD because payment = network fee - cashCow.SendToAddress(invoiceAddress, networkFee); - Thread.Sleep(1000); - - // pay remaining amount - cashCow.SendToAddress(invoiceAddress, 4 * networkFee); - Thread.Sleep(1000); - - await TestUtils.EventuallyAsync(async () => - { - var parsedJson = await GetExport(user); - Assert.Equal(3, parsedJson.Length); - - var invoiceDueAfterFirstPayment = 3 * networkFee.ToDecimal(MoneyUnit.BTC) * invoice.Rate; - var pay1str = parsedJson[0].ToString(); - Assert.Contains("\"InvoiceItemDesc\": \"Some \\\", description\"", pay1str); - Assert.Equal(invoiceDueAfterFirstPayment, GetFieldValue(pay1str, "InvoiceDue")); - Assert.Contains("\"InvoicePrice\": 10.0", pay1str); - Assert.Contains("\"ConversionRate\": 5000.0", pay1str); - Assert.Contains($"\"InvoiceId\": \"{invoice.Id}\",", pay1str); - - var pay2str = parsedJson[1].ToString(); - Assert.Equal(invoiceDueAfterFirstPayment, GetFieldValue(pay2str, "InvoiceDue")); - - var pay3str = parsedJson[2].ToString(); - Assert.Contains("\"InvoiceDue\": 0", pay3str); - }); - - // create an invoice for a new store and check responses with and without store id - var otherUser = tester.NewAccount(); - await otherUser.GrantAccessAsync(); - otherUser.RegisterDerivationScheme("BTC"); - await otherUser.SetNetworkFeeMode(NetworkFeeMode.Always); - var newInvoice = await otherUser.BitPay.CreateInvoiceAsync( - new Invoice - { - Price = 21, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some \", description", - FullNotifications = true - }, Facade.Merchant); - - await otherUser.PayInvoice(newInvoice.Id); - Assert.Single(await GetExport(otherUser)); - Assert.Single(await GetExport(otherUser, otherUser.StoreId)); - Assert.Equal(3, (await GetExport(user, user.StoreId)).Length); - Assert.Equal(3, (await GetExport(user)).Length); - - await otherUser.AddOwner(user.UserId); - Assert.Equal(4, (await GetExport(user)).Length); - Assert.Single(await GetExport(user, otherUser.StoreId)); - } - [Fact(Timeout = LongRunningTestTimeout)] [Trait("Integration", "Integration")] public async Task CanChangeNetworkFeeMode() @@ -1892,45 +1789,6 @@ namespace BTCPayServer.Tests } } - [Fact(Timeout = LongRunningTestTimeout)] - [Trait("Integration", "Integration")] - public async Task CanExportInvoicesCsv() - { - using var tester = CreateServerTester(); - await tester.StartAsync(); - var user = tester.NewAccount(); - await user.GrantAccessAsync(); - user.RegisterDerivationScheme("BTC"); - await user.SetNetworkFeeMode(NetworkFeeMode.Always); - var invoice = user.BitPay.CreateInvoice( - new Invoice - { - Price = 500, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some \", description", - FullNotifications = true - }, Facade.Merchant); - - var cashCow = tester.ExplorerNode; - var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); - var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Coins(0.001m); - cashCow.SendToAddress(invoiceAddress, firstPayment); - TestUtils.Eventually(() => - { - var exportResultPaid = - user.GetController().Export("csv").GetAwaiter().GetResult(); - var paidresult = Assert.IsType(exportResultPaid); - Assert.Equal("application/csv", paidresult.ContentType); - Assert.Contains($",orderId,{invoice.Id},", paidresult.Content); - Assert.Contains($",On-Chain,BTC,0.0991,0.0001,5000.0", paidresult.Content); - Assert.Contains($",USD,5.00", paidresult.Content); // Seems hacky but some plateform does not render this decimal the same - Assert.Contains("0,,\"Some \"\", description\",New (paidPartial),new,paidPartial", - paidresult.Content); - }); - } - [Fact(Timeout = LongRunningTestTimeout)] [Trait("Integration", "Integration")] public async Task CanCreateAndDeleteApps() diff --git a/BTCPayServer/Controllers/UIInvoiceController.UI.cs b/BTCPayServer/Controllers/UIInvoiceController.UI.cs index 8ad59eff8..81235dad8 100644 --- a/BTCPayServer/Controllers/UIInvoiceController.UI.cs +++ b/BTCPayServer/Controllers/UIInvoiceController.UI.cs @@ -24,7 +24,6 @@ using BTCPayServer.Rating; using BTCPayServer.Services; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Invoices; -using BTCPayServer.Services.Invoices.Export; using BTCPayServer.Services.Rates; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -1182,42 +1181,6 @@ namespace BTCPayServer.Controllers }; } - [HttpGet] - [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewInvoices)] - [BitpayAPIConstraint(false)] - public async Task Export(string format, string? storeId = null, string? searchTerm = null, int timezoneOffset = 0) - { - var model = new InvoiceExport(_CurrencyNameTable); - var fs = new SearchString(searchTerm); - var storeIds = new HashSet(); - if (storeId is not null) - { - storeIds.Add(storeId); - } - if (fs.GetFilterArray("storeid") is { } l) - { - foreach (var i in l) - storeIds.Add(i); - } - - var apps = await _appService.GetAllApps(GetUserId(), false, storeId); - InvoiceQuery invoiceQuery = GetInvoiceQuery(fs, apps, timezoneOffset); - invoiceQuery.StoreId = storeIds.ToArray(); - invoiceQuery.Skip = 0; - invoiceQuery.Take = int.MaxValue; - var invoices = await _InvoiceRepository.GetInvoices(invoiceQuery); - var res = model.Process(invoices, format); - - var cd = new ContentDisposition - { - FileName = $"btcpay-export-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture)}.{format}", - Inline = true - }; - Response.Headers.Add("Content-Disposition", cd.ToString()); - Response.Headers.Add("X-Content-Type-Options", "nosniff"); - return Content(res, "application/" + format); - } - private SelectList GetPaymentMethodsSelectList() { var store = GetCurrentStore(); diff --git a/BTCPayServer/Services/Invoices/Export/InvoiceExport.cs b/BTCPayServer/Services/Invoices/Export/InvoiceExport.cs deleted file mode 100644 index ff0a2e2c3..000000000 --- a/BTCPayServer/Services/Invoices/Export/InvoiceExport.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using BTCPayServer.Services.Rates; -using CsvHelper.Configuration; -using Newtonsoft.Json; - -namespace BTCPayServer.Services.Invoices.Export -{ - public class InvoiceExport - { - public BTCPayNetworkProvider Networks { get; } - public CurrencyNameTable Currencies { get; } - - public InvoiceExport(CurrencyNameTable currencies) - { - Currencies = currencies; - } - public string Process(InvoiceEntity[] invoices, string fileFormat) - { - var csvInvoices = new List(); - foreach (var i in invoices) - { - csvInvoices.AddRange(convertFromDb(i)); - } - - if (String.Equals(fileFormat, "json", StringComparison.OrdinalIgnoreCase)) - return processJson(csvInvoices); - else if (String.Equals(fileFormat, "csv", StringComparison.OrdinalIgnoreCase)) - return processCsv(csvInvoices); - else - throw new Exception("Export format not supported"); - } - - private string processJson(List invoices) - { - var serializerSett = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; - var json = JsonConvert.SerializeObject(invoices, Formatting.Indented, serializerSett); - - return json; - } - - private string processCsv(List invoices) - { - using StringWriter writer = new StringWriter(); - using var csvWriter = new CsvHelper.CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture), true); - csvWriter.WriteHeader(); - csvWriter.NextRecord(); - csvWriter.WriteRecords(invoices); - csvWriter.Flush(); - return writer.ToString(); - } - - private IEnumerable convertFromDb(InvoiceEntity invoice) - { - var exportList = new List(); - var currency = Currencies.GetNumberFormatInfo(invoice.Currency, true); - var invoiceDue = invoice.Price; - var payments = invoice.GetPayments(false); - // Get invoices with payments - if (payments.Count > 0) - { - foreach (var payment in payments) - { - var pdata = payment.GetCryptoPaymentData(); - invoiceDue -= payment.InvoicePaidAmount.Net; - - var target = new ExportInvoiceHolder - { - ReceivedDate = payment.ReceivedTime.UtcDateTime, - PaymentId = pdata.GetPaymentId(), - CryptoCode = payment.Currency, - ConversionRate = payment.Rate, - PaymentType = payment.GetPaymentMethodId().PaymentType.ToPrettyString(), - Destination = pdata.GetDestination(), - Paid = payment.PaidAmount.Gross.ToString(CultureInfo.InvariantCulture), - PaidCurrency = Math.Round(payment.InvoicePaidAmount.Gross, currency.NumberDecimalDigits).ToString(CultureInfo.InvariantCulture), - // Adding NetworkFee because Paid doesn't take into account network fees - // so if fee is 10000 satoshis, customer can essentially send infinite number of tx - // and merchant effectivelly would receive 0 BTC, invoice won't be paid - // while looking just at export you could sum Paid and assume merchant "received payments" - NetworkFee = payment.NetworkFee.ToString(CultureInfo.InvariantCulture), - InvoiceDue = Math.Round(invoiceDue, currency.NumberDecimalDigits), - OrderId = invoice.Metadata.OrderId ?? string.Empty, - StoreId = invoice.StoreId, - InvoiceId = invoice.Id, - InvoiceCreatedDate = invoice.InvoiceTime.UtcDateTime, - InvoiceExpirationDate = invoice.ExpirationTime.UtcDateTime, - InvoiceMonitoringDate = invoice.MonitoringExpiration.UtcDateTime, -#pragma warning disable CS0618 // Type or member is obsolete - InvoiceFullStatus = invoice.GetInvoiceState().ToString(), - InvoiceStatus = invoice.StatusString, - InvoiceExceptionStatus = invoice.ExceptionStatusString, -#pragma warning restore CS0618 // Type or member is obsolete - InvoiceItemCode = invoice.Metadata.ItemCode, - InvoiceItemDesc = invoice.Metadata.ItemDesc, - InvoicePrice = invoice.Price, - InvoiceCurrency = invoice.Currency, - BuyerEmail = invoice.Metadata.BuyerEmail, - Accounted = payment.Accounted - }; - - exportList.Add(target); - } - } - else - { - var target = new ExportInvoiceHolder - { - InvoiceDue = Math.Round(invoiceDue, currency.NumberDecimalDigits), - OrderId = invoice.Metadata.OrderId ?? string.Empty, - StoreId = invoice.StoreId, - InvoiceId = invoice.Id, - InvoiceCreatedDate = invoice.InvoiceTime.UtcDateTime, - InvoiceExpirationDate = invoice.ExpirationTime.UtcDateTime, - InvoiceMonitoringDate = invoice.MonitoringExpiration.UtcDateTime, -#pragma warning disable CS0618 // Type or member is obsolete - InvoiceFullStatus = invoice.GetInvoiceState().ToString(), - InvoiceStatus = invoice.StatusString, - InvoiceExceptionStatus = invoice.ExceptionStatusString, -#pragma warning restore CS0618 // Type or member is obsolete - InvoiceItemCode = invoice.Metadata.ItemCode, - InvoiceItemDesc = invoice.Metadata.ItemDesc, - InvoicePrice = invoice.Price, - InvoiceCurrency = invoice.Currency, - BuyerEmail = invoice.Metadata.BuyerEmail - }; - - exportList.Add(target); - } - - exportList = exportList.OrderBy(a => a.ReceivedDate).ToList(); - - return exportList; - } - } - - public class ExportInvoiceHolder - { - public DateTime? ReceivedDate { get; set; } - public string StoreId { get; set; } - public string OrderId { get; set; } - public string InvoiceId { get; set; } - public DateTime InvoiceCreatedDate { get; set; } - public DateTime InvoiceExpirationDate { get; set; } - public DateTime InvoiceMonitoringDate { get; set; } - - public string PaymentId { get; set; } - public string Destination { get; set; } - public string PaymentType { get; set; } - public string CryptoCode { get; set; } - public string Paid { get; set; } - public string NetworkFee { get; set; } - public decimal ConversionRate { get; set; } - public string PaidCurrency { get; set; } - public string InvoiceCurrency { get; set; } - public decimal InvoiceDue { get; set; } - public decimal InvoicePrice { get; set; } - public string InvoiceItemCode { get; set; } - public string InvoiceItemDesc { get; set; } - public string InvoiceFullStatus { get; set; } - public string InvoiceStatus { get; set; } - public string InvoiceExceptionStatus { get; set; } - public string BuyerEmail { get; set; } - public bool Accounted { get; set; } - } -} diff --git a/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml b/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml index ab4f4bbfb..86a9d9c6d 100644 --- a/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml +++ b/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml @@ -1,10 +1,13 @@ @using BTCPayServer.Client @using BTCPayServer.Client.Models @using BTCPayServer.Services + @inject DisplayFormatter DisplayFormatter +@inject ReportService ReportService @inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary @model InvoicesModel @{ + var reportNames = ReportService.ReportProviders.Select(p => p.Value.Name).OrderBy(c => c).ToArray(); ViewData.SetActivePage(InvoiceNavPages.Index, "Invoices"); var statusFilterCount = CountArrayFilter("status") + CountArrayFilter("exceptionstatus") + (HasBooleanFilter("includearchived") ? 1 : 0) + (HasBooleanFilter("unusual") ? 1 : 0); var hasDateFilter = HasArrayFilter("startdate") || HasArrayFilter("enddate"); @@ -116,9 +119,6 @@ document.addEventListener("DOMContentLoaded", function () { var timezoneOffset = new Date().getTimezoneOffset(); - $(".export-link, a.dropdown-item").each(function () { - this.href = this.href.replace("timezoneoffset=0", "timezoneoffset=" + timezoneOffset); - }); $("#invoices") .on("click", ".invoice-row .invoice-details-toggle", function (e) { @@ -335,16 +335,18 @@ { } - +