From 6e7f1151bc4af6e65796b52946749fb3a4315a97 Mon Sep 17 00:00:00 2001 From: Kukks Date: Sat, 5 Jan 2019 19:47:39 +0100 Subject: [PATCH] bug fixes and optimizations --- BTCPayServer.Tests/CrowdfundTests.cs | 52 ++++++------ BTCPayServer.Tests/UnitTest1.cs | 85 ++++++++++--------- .../Controllers/AppsController.Crowdsale.cs | 15 +++- BTCPayServer/Controllers/AppsController.cs | 53 ++---------- .../Controllers/AppsPublicController.cs | 45 +++++++++- .../Crowdfund/CrowdfundHubStreamer.cs | 60 +++++++++++-- BTCPayServer/Models/InvoiceResponse.cs | 6 ++ .../Services/Invoices/InvoiceEntity.cs | 1 + 8 files changed, 190 insertions(+), 127 deletions(-) diff --git a/BTCPayServer.Tests/CrowdfundTests.cs b/BTCPayServer.Tests/CrowdfundTests.cs index 50c7c24e8..23449ff89 100644 --- a/BTCPayServer.Tests/CrowdfundTests.cs +++ b/BTCPayServer.Tests/CrowdfundTests.cs @@ -2,9 +2,12 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using BTCPayServer.Controllers; using BTCPayServer.Data; +using BTCPayServer.Events; +using BTCPayServer.Hubs; using BTCPayServer.Models; using BTCPayServer.Models.AppViewModels; using BTCPayServer.Models.StoreViewModels; @@ -51,7 +54,7 @@ namespace BTCPayServer.Tests vm.Name = "test"; vm.SelectedAppType = AppType.Crowdfund.ToString(); var redirectToAction = Assert.IsType(apps.CreateApp(vm).Result); - Assert.Equal(nameof(apps.UpdatePointOfSale), redirectToAction.ActionName); + Assert.Equal(nameof(apps.UpdateCrowdfund), redirectToAction.ActionName); var appList = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model); var appList2 = Assert.IsType(Assert.IsType(apps2.ListApps().Result).Model); @@ -93,6 +96,7 @@ namespace BTCPayServer.Tests //Scenario 1: Not Enabled - Not Allowed var crowdfundViewModel = Assert.IsType(Assert .IsType(apps.UpdateCrowdfund(appId).Result).Model); + crowdfundViewModel.TargetCurrency = "BTC"; crowdfundViewModel.Enabled = false; crowdfundViewModel.EndDate = null; @@ -115,14 +119,15 @@ namespace BTCPayServer.Tests RedirectToCheckout = false, Amount = new decimal(0.01) })); - Assert.IsType(await publicApps.ViewCrowdfund(appId, string.Empty)); + Assert.IsType(await publicApps.ViewCrowdfund(appId, string.Empty)); + Assert.IsType(await anonAppPubsController.ViewCrowdfund(appId, string.Empty)); //Scenario 3: Enabled But Start Date > Now - Not Allowed crowdfundViewModel.StartDate= DateTime.Today.AddDays(2); crowdfundViewModel.Enabled = true; Assert.IsType(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result); - Assert.IsType(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() + Assert.IsType(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() { Amount = new decimal(0.01) })); @@ -134,7 +139,7 @@ namespace BTCPayServer.Tests crowdfundViewModel.Enabled = true; Assert.IsType(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result); - Assert.IsType(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() + Assert.IsType(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() { Amount = new decimal(0.01) })); @@ -148,13 +153,13 @@ namespace BTCPayServer.Tests crowdfundViewModel.TargetCurrency = "BTC"; crowdfundViewModel.EnforceTargetAmount = true; Assert.IsType(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result); - Assert.IsType(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() + Assert.IsType(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() { Amount = new decimal(1.01) })); //Scenario 6: Allowed - Assert.IsType(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() + Assert.IsType(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() { Amount = new decimal(0.05) })); @@ -164,7 +169,7 @@ namespace BTCPayServer.Tests [Fact] [Trait("Integration", "Integration")] - public async Task CanComputeCrowdfundModel() + public void CanComputeCrowdfundModel() { using (var tester = ServerTester.Create()) { @@ -200,9 +205,10 @@ namespace BTCPayServer.Tests Assert.Equal(crowdfundViewModel.EndDate, model.EndDate ); Assert.Equal(crowdfundViewModel.StartDate, model.StartDate ); Assert.Equal(crowdfundViewModel.TargetCurrency, model.TargetCurrency ); - Assert.Equal(model.Info.CurrentAmount, 0m); - Assert.Equal(model.Info.CurrentPendingAmount, 0m); - Assert.Equal(model.Info.ProgressPercentage, 0m); + Assert.Equal(0m, model.Info.CurrentAmount ); + Assert.Equal(0m, model.Info.CurrentPendingAmount); + Assert.Equal(0m, model.Info.ProgressPercentage); + var invoice = user.BitPay.CreateInvoice(new Invoice() @@ -211,8 +217,9 @@ namespace BTCPayServer.Tests Price = 1m, Currency = "BTC", PosData = "posData", - OrderId = "orderId", + OrderId = $"{CrowdfundHubStreamer.CrowdfundInvoiceOrderIdPrefix}{appId}", ItemDesc = "Some description", + TransactionSpeed = "high", FullNotifications = true }, Facade.Merchant); @@ -221,27 +228,18 @@ namespace BTCPayServer.Tests .IsType(publicApps.ViewCrowdfund(appId, String.Empty).Result).Model); var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network); - Assert.Equal(model.Info.CurrentAmount, 0m); - Assert.Equal(model.Info.CurrentPendingAmount, 1m); - Assert.Equal(model.Info.ProgressPercentage, 0m); - Assert.Equal(model.Info.PendingProgressPercentage, 1m); + tester.ExplorerNode.SendToAddress(invoiceAddress,invoice.BtcDue); + Assert.Equal(0m ,model.Info.CurrentAmount ); + Assert.Equal(1m, model.Info.CurrentPendingAmount); + Assert.Equal( 0m, model.Info.ProgressPercentage); + Assert.Equal(1m, model.Info.PendingProgressPercentage); - UnitTest1.Eventually(() => - { - var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); - Assert.Equal(InvoiceState.ToString(InvoiceStatus.Complete), localInvoice.Status); - }); - model = Assert.IsType(Assert - .IsType(publicApps.ViewCrowdfund(appId, String.Empty).Result).Model); - - Assert.Equal(model.Info.CurrentAmount, 1m); - Assert.Equal(model.Info.CurrentPendingAmount, 0m); - Assert.Equal(model.Info.ProgressPercentage, 1m); - Assert.Equal(model.Info.PendingProgressPercentage, 0m); + + } diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 9422d7741..1f50f2287 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -326,7 +326,7 @@ namespace BTCPayServer.Tests var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network); tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Satoshis((decimal)invoice.BtcDue.Satoshi * 0.75m)); - Eventually(() => + TestUtils.Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("paid", localInvoice.Status); @@ -448,7 +448,7 @@ namespace BTCPayServer.Tests }); await Task.Delay(TimeSpan.FromMilliseconds(1000)); // Give time to listen the new invoices await tester.SendLightningPaymentAsync(invoice); - await EventuallyAsync(async () => + await TestUtils.EventuallyAsync(async () => { var localInvoice = await user.BitPay.GetInvoiceAsync(invoice.Id); Assert.Equal("complete", localInvoice.Status); @@ -651,7 +651,7 @@ namespace BTCPayServer.Tests var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Satoshis(10); cashCow.SendToAddress(invoiceAddress, firstPayment); - Eventually(() => + TestUtils.Eventually(() => { invoice = acc.BitPay.GetInvoice(invoice.Id); Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid); @@ -747,7 +747,7 @@ namespace BTCPayServer.Tests var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, user.SupportedNetwork.NBitcoinNetwork); Logs.Tester.LogInformation($"The invoice should be paidOver"); - Eventually(() => + TestUtils.Eventually(() => { invoice = user.BitPay.GetInvoice(invoice.Id); Assert.Equal(payment1, invoice.BtcPaid); @@ -776,7 +776,7 @@ namespace BTCPayServer.Tests Assert.Equal(tx2, ((NewTransactionEvent)listener.NextEvent(cts.Token)).TransactionData.TransactionHash); } Logs.Tester.LogInformation($"The invoice should now not be paidOver anymore"); - Eventually(() => + TestUtils.Eventually(() => { invoice = user.BitPay.GetInvoice(invoice.Id); Assert.Equal(payment2, invoice.BtcPaid); @@ -1023,7 +1023,7 @@ namespace BTCPayServer.Tests var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); var firstPayment = Money.Coins(0.1m); cashCow.SendToAddress(invoiceAddress, firstPayment); - Eventually(() => + TestUtils.Eventually(() => { invoice = user.BitPay.GetInvoice(invoice.Id); Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid); @@ -1044,7 +1044,7 @@ namespace BTCPayServer.Tests Assert.NotEqual(invoice.BtcDue, invoice.CryptoInfo[0].Due); // Should be BTC rate cashCow.SendToAddress(invoiceAddress, invoice.CryptoInfo[0].Due); - Eventually(() => + TestUtils.Eventually(() => { invoice = user.BitPay.GetInvoice(invoice.Id); Assert.Equal("paid", invoice.Status); @@ -1142,7 +1142,7 @@ namespace BTCPayServer.Tests var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); var firstPayment = Money.Coins(0.04m); cashCow.SendToAddress(invoiceAddress, firstPayment); - Eventually(() => + TestUtils.Eventually(() => { invoice = user.BitPay.GetInvoice(invoice.Id); Assert.True(invoice.BtcPaid == firstPayment); @@ -1184,7 +1184,7 @@ namespace BTCPayServer.Tests firstPayment = Money.Coins(0.04m); cashCow.SendToAddress(invoiceAddress, firstPayment); Logs.Tester.LogInformation("First payment sent to " + invoiceAddress); - Eventually(() => + TestUtils.Eventually(() => { invoice = user.BitPay.GetInvoice(invoice.Id); Assert.True(invoice.BtcPaid == firstPayment); @@ -1198,7 +1198,7 @@ namespace BTCPayServer.Tests cashCow.Generate(2); // LTC is not worth a lot, so just to make sure we have money... cashCow.SendToAddress(invoiceAddress, secondPayment); Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress); - Eventually(() => + TestUtils.Eventually(() => { invoice = user.BitPay.GetInvoice(invoice.Id); Assert.Equal(Money.Zero, invoice.BtcDue); @@ -1632,7 +1632,7 @@ donation: cashCow.SendToAddress(invoiceAddress, 4*networkFee); Thread.Sleep(1000); - Eventually(() => + TestUtils.Eventually(() => { var jsonResultPaid = user.GetController().Export("json").GetAwaiter().GetResult(); var paidresult = Assert.IsType(jsonResultPaid); @@ -1683,7 +1683,7 @@ donation: var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Satoshis(10); cashCow.SendToAddress(invoiceAddress, firstPayment); - Eventually(() => + TestUtils.Eventually(() => { var exportResultPaid = user.GetController().Export("csv").GetAwaiter().GetResult(); var paidresult = Assert.IsType(exportResultPaid); @@ -1758,7 +1758,7 @@ donation: Assert.Equal(0, invoice.CryptoInfo[0].TxCount); Assert.True(invoice.MinerFees.ContainsKey("BTC")); Assert.Contains(invoice.MinerFees["BTC"].SatoshiPerBytes, new[] { 100.0m, 20.0m }); - Eventually(() => + TestUtils.Eventually(() => { var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery() { @@ -1804,7 +1804,7 @@ donation: Money secondPayment = Money.Zero; - Eventually(() => + TestUtils.Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("new", localInvoice.Status); @@ -1827,7 +1827,7 @@ donation: cashCow.SendToAddress(invoiceAddress, secondPayment); - Eventually(() => + TestUtils.Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("paid", localInvoice.Status); @@ -1841,7 +1841,7 @@ donation: cashCow.Generate(1); //The user has medium speed settings, so 1 conf is enough to be confirmed - Eventually(() => + TestUtils.Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("confirmed", localInvoice.Status); @@ -1849,7 +1849,7 @@ donation: cashCow.Generate(5); //Now should be complete - Eventually(() => + TestUtils.Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("complete", localInvoice.Status); @@ -1871,7 +1871,7 @@ donation: var txId = cashCow.SendToAddress(invoiceAddress, invoice.BtcDue + Money.Coins(1)); - Eventually(() => + TestUtils.Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("paid", localInvoice.Status); @@ -1888,7 +1888,7 @@ donation: cashCow.Generate(1); - Eventually(() => + TestUtils.Eventually(() => { var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); Assert.Equal("confirmed", localInvoice.Status); @@ -2078,36 +2078,39 @@ donation: return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.GetAddress() == h) != null; } - public static void Eventually(Action act) + public static class TestUtils { - CancellationTokenSource cts = new CancellationTokenSource(20000); - while (true) + public static void Eventually(Action act) { - try + CancellationTokenSource cts = new CancellationTokenSource(20000); + while (true) { - act(); - break; - } - catch (XunitException) when (!cts.Token.IsCancellationRequested) - { - cts.Token.WaitHandle.WaitOne(500); + try + { + act(); + break; + } + catch (XunitException) when (!cts.Token.IsCancellationRequested) + { + cts.Token.WaitHandle.WaitOne(500); + } } } - } - private async Task EventuallyAsync(Func act) - { - CancellationTokenSource cts = new CancellationTokenSource(20000); - while (true) + public static async Task EventuallyAsync(Func act) { - try + CancellationTokenSource cts = new CancellationTokenSource(20000); + while (true) { - await act(); - break; - } - catch (XunitException) when (!cts.Token.IsCancellationRequested) - { - await Task.Delay(500); + try + { + await act(); + break; + } + catch (XunitException) when (!cts.Token.IsCancellationRequested) + { + await Task.Delay(500); + } } } } diff --git a/BTCPayServer/Controllers/AppsController.Crowdsale.cs b/BTCPayServer/Controllers/AppsController.Crowdsale.cs index 9fc33c36f..add0c86d9 100644 --- a/BTCPayServer/Controllers/AppsController.Crowdsale.cs +++ b/BTCPayServer/Controllers/AppsController.Crowdsale.cs @@ -11,6 +11,8 @@ namespace BTCPayServer.Controllers public class CrowdfundAppUpdated { public string AppId { get; set; } + public CrowdfundSettings Settings { get; set; } + public string StoreId { get; set; } } public class CrowdfundSettings @@ -83,7 +85,7 @@ namespace BTCPayServer.Controllers [Route("{appId}/settings/crowdfund")] public async Task UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm) { - if (_AppsHelper.GetCurrencyData(vm.TargetCurrency, false) == null) + if (!string.IsNullOrEmpty( vm.TargetCurrency) && _AppsHelper.GetCurrencyData(vm.TargetCurrency, false) == null) ModelState.AddModelError(nameof(vm.TargetCurrency), "Invalid currency"); try @@ -109,7 +111,8 @@ namespace BTCPayServer.Controllers var app = await GetOwnedApp(appId, AppType.Crowdfund); if (app == null) return NotFound(); - app.SetSettings(new CrowdfundSettings() + + var newSettings = new CrowdfundSettings() { Title = vm.Title, Enabled = vm.Enabled, @@ -133,11 +136,15 @@ namespace BTCPayServer.Controllers ResetEvery = Enum.Parse(vm.ResetEvery), UseInvoiceAmount = vm.UseInvoiceAmount, UseAllStoreInvoices = vm.UseAllStoreInvoices - }); + }; + + app.SetSettings(newSettings); await UpdateAppSettings(app); _EventAggregator.Publish(new CrowdfundAppUpdated() { - AppId = appId + AppId = appId, + StoreId = app.StoreDataId, + Settings = newSettings }); StatusMessage = "App updated"; return RedirectToAction(nameof(ListApps)); diff --git a/BTCPayServer/Controllers/AppsController.cs b/BTCPayServer/Controllers/AppsController.cs index fac2c40f7..07618ca59 100644 --- a/BTCPayServer/Controllers/AppsController.cs +++ b/BTCPayServer/Controllers/AppsController.cs @@ -47,7 +47,7 @@ namespace BTCPayServer.Controllers public async Task ListApps() { - var apps = await GetAllApps(); + var apps = await _AppsHelper.GetAllApps(GetUserId()); return View(new ListAppsViewModel() { Apps = apps @@ -61,7 +61,7 @@ namespace BTCPayServer.Controllers var appData = await GetOwnedApp(appId); if (appData == null) return NotFound(); - if (await DeleteApp(appData)) + if (await _AppsHelper.DeleteApp(appData)) StatusMessage = "App removed successfully"; return RedirectToAction(nameof(ListApps)); } @@ -70,7 +70,7 @@ namespace BTCPayServer.Controllers [Route("create")] public async Task CreateApp() { - var stores = await GetOwnedStores(); + var stores = await _AppsHelper.GetOwnedStores(GetUserId()); if (stores.Length == 0) { StatusMessage = "Error: You must have created at least one store"; @@ -85,7 +85,7 @@ namespace BTCPayServer.Controllers [Route("create")] public async Task CreateApp(CreateAppViewModel vm) { - var stores = await GetOwnedStores(); + var stores = await _AppsHelper.GetOwnedStores(GetUserId()); if (stores.Length == 0) { StatusMessage = "Error: You must own at least one store"; @@ -153,50 +153,7 @@ namespace BTCPayServer.Controllers return _AppsHelper.GetAppDataIfOwner(GetUserId(), appId, type); } - private async Task GetOwnedStores() - { - var userId = GetUserId(); - using (var ctx = _ContextFactory.CreateContext()) - { - return await ctx.UserStore - .Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner) - .Select(u => u.StoreData) - .ToArrayAsync(); - } - } - - private async Task DeleteApp(AppData appData) - { - using (var ctx = _ContextFactory.CreateContext()) - { - ctx.Apps.Add(appData); - ctx.Entry(appData).State = EntityState.Deleted; - return await ctx.SaveChangesAsync() == 1; - } - } - - private async Task GetAllApps() - { - var userId = GetUserId(); - using (var ctx = _ContextFactory.CreateContext()) - { - return await ctx.UserStore - .Where(us => us.ApplicationUserId == userId) - .Join(ctx.Apps, us => us.StoreDataId, app => app.StoreDataId, - (us, app) => - new ListAppsViewModel.ListAppViewModel() - { - IsOwner = us.Role == StoreRoles.Owner, - StoreId = us.StoreDataId, - StoreName = us.StoreData.StoreName, - AppName = app.Name, - AppType = app.AppType, - Id = app.Id - }) - .ToArrayAsync(); - } - } - + private string GetUserId() { return _UserManager.GetUserId(User); diff --git a/BTCPayServer/Controllers/AppsPublicController.cs b/BTCPayServer/Controllers/AppsPublicController.cs index cb12301e6..fa3722fba 100644 --- a/BTCPayServer/Controllers/AppsPublicController.cs +++ b/BTCPayServer/Controllers/AppsPublicController.cs @@ -151,7 +151,7 @@ namespace BTCPayServer.Controllers price = request.Amount; } - if (isAdmin || (settings.EnforceTargetAmount && info.TargetAmount.HasValue && price > + if (!isAdmin && (settings.EnforceTargetAmount && info.TargetAmount.HasValue && price > (info.TargetAmount - (info.Info.CurrentAmount + info.Info.CurrentPendingAmount)))) { return NotFound("Contribution Amount is more than is currently allowed."); @@ -264,6 +264,49 @@ namespace BTCPayServer.Controllers _Currencies = currencies; } + + public async Task GetOwnedStores(string userId) + { + using (var ctx = _ContextFactory.CreateContext()) + { + return await ctx.UserStore + .Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner) + .Select(u => u.StoreData) + .ToArrayAsync(); + } + } + + public async Task DeleteApp(AppData appData) + { + using (var ctx = _ContextFactory.CreateContext()) + { + ctx.Apps.Add(appData); + ctx.Entry(appData).State = EntityState.Deleted; + return await ctx.SaveChangesAsync() == 1; + } + } + + public async Task GetAllApps(string userId, bool allowNoUser = false) + { + using (var ctx = _ContextFactory.CreateContext()) + { + return await ctx.UserStore + .Where(us => (allowNoUser && string.IsNullOrEmpty(userId) ) || us.ApplicationUserId == userId) + .Join(ctx.Apps, us => us.StoreDataId, app => app.StoreDataId, + (us, app) => + new ListAppsViewModel.ListAppViewModel() + { + IsOwner = us.Role == StoreRoles.Owner, + StoreId = us.StoreDataId, + StoreName = us.StoreData.StoreName, + AppName = app.Name, + AppType = app.AppType, + Id = app.Id + }) + .ToArrayAsync(); + } + } + public async Task GetApp(string appId, AppType appType, bool includeStore = false) { diff --git a/BTCPayServer/Crowdfund/CrowdfundHubStreamer.cs b/BTCPayServer/Crowdfund/CrowdfundHubStreamer.cs index e31dac6bf..32ea8a6d9 100644 --- a/BTCPayServer/Crowdfund/CrowdfundHubStreamer.cs +++ b/BTCPayServer/Crowdfund/CrowdfundHubStreamer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -15,6 +16,8 @@ using BTCPayServer.Services.Rates; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Primitives; +using NBitcoin; +using YamlDotNet.Core; namespace BTCPayServer.Hubs { @@ -28,6 +31,9 @@ namespace BTCPayServer.Hubs private readonly RateFetcher _RateFetcher; private readonly BTCPayNetworkProvider _BtcPayNetworkProvider; private readonly InvoiceRepository _InvoiceRepository; + private readonly ConcurrentDictionary _QuickAppInvoiceLookup = + new ConcurrentDictionary(); + public CrowdfundHubStreamer(EventAggregator eventAggregator, IHubContext hubContext, IMemoryCache memoryCache, @@ -43,9 +49,36 @@ namespace BTCPayServer.Hubs _RateFetcher = rateFetcher; _BtcPayNetworkProvider = btcPayNetworkProvider; _InvoiceRepository = invoiceRepository; +#pragma warning disable 4014 + InitLookup(); +#pragma warning restore 4014 SubscribeToEvents(); } - + + private async Task InitLookup() + { + var apps = await _AppsHelper.GetAllApps(null, true); + apps = apps.Where(model => Enum.Parse(model.AppType) == AppType.Crowdfund).ToArray(); + var tasks = new List(); + tasks.AddRange(apps.Select(app => Task.Run(async () => + { + var fullApp = await _AppsHelper.GetApp(app.Id, AppType.Crowdfund, false); + var settings = fullApp.GetSettings(); + UpdateLookup(app.Id, app.StoreId, settings); + }))); + await Task.WhenAll(tasks); + } + + private void UpdateLookup(string appId, string storeId, AppsController.CrowdfundSettings settings) + { + _QuickAppInvoiceLookup.AddOrReplace(storeId, + ( + appId: appId, + useAllStoreInvoices: settings?.UseAllStoreInvoices ?? false, + useInvoiceAmount: settings?.UseInvoiceAmount ?? false + )); + } + public Task GetCrowdfundInfo(string appId) { return _MemoryCache.GetOrCreateAsync(GetCacheKey(appId), async entry => @@ -80,6 +113,7 @@ namespace BTCPayServer.Hubs _EventAggregator.Subscribe(OnInvoiceEvent); _EventAggregator.Subscribe(updated => { + UpdateLookup(updated.AppId, updated.StoreId, updated.Settings); InvalidateCacheForApp(updated.AppId); }); } @@ -95,12 +129,20 @@ namespace BTCPayServer.Hubs { return; } - var appId = invoiceEvent.Invoice.OrderId.Replace(CrowdfundInvoiceOrderIdPrefix, "", StringComparison.InvariantCultureIgnoreCase); + + if (!_QuickAppInvoiceLookup.TryGetValue(invoiceEvent.Invoice.StoreId, out var quickLookup) || + (!quickLookup.useAllStoreInvoices && + !invoiceEvent.Invoice.OrderId.Equals($"{CrowdfundInvoiceOrderIdPrefix}{quickLookup.appId}", StringComparison.InvariantCulture) + )) + { + return; + } + switch (invoiceEvent.Name) { case InvoiceEvent.ReceivedPayment: var data = invoiceEvent.Payment.GetCryptoPaymentData(); - _HubContext.Clients.Group(appId).SendCoreAsync(CrowdfundHub.PaymentReceived, new object[] + _HubContext.Clients.Group(quickLookup.appId).SendCoreAsync(CrowdfundHub.PaymentReceived, new object[] { data.GetValue(), invoiceEvent.Payment.GetCryptoCode(), @@ -108,10 +150,16 @@ namespace BTCPayServer.Hubs invoiceEvent.Payment.GetPaymentMethodId().PaymentType) } ); - InvalidateCacheForApp(appId); + InvalidateCacheForApp(quickLookup.appId); + break; + case InvoiceEvent.Created: + if (quickLookup.useInvoiceAmount) + { + InvalidateCacheForApp(quickLookup.appId); + } break; case InvoiceEvent.Completed: - InvalidateCacheForApp(appId); + InvalidateCacheForApp(quickLookup.appId); break; } } @@ -123,7 +171,7 @@ namespace BTCPayServer.Hubs GetCrowdfundInfo(appId).ContinueWith(task => { _HubContext.Clients.Group(appId).SendCoreAsync(CrowdfundHub.InfoUpdated, new object[]{ task.Result} ); - }, TaskScheduler.Default); + }, TaskScheduler.Current); } diff --git a/BTCPayServer/Models/InvoiceResponse.cs b/BTCPayServer/Models/InvoiceResponse.cs index df47d65a9..486a3b906 100644 --- a/BTCPayServer/Models/InvoiceResponse.cs +++ b/BTCPayServer/Models/InvoiceResponse.cs @@ -40,6 +40,12 @@ namespace BTCPayServer.Models //{"facade":"pos/invoice","data":{,}} public class InvoiceResponse { + [JsonIgnore] + public string StoreId + { + get; set; + } + //"url":"https://test.bitpay.com/invoice?id=9saCHtp1zyPcNoi3rDdBu8" [JsonProperty("url")] public string Url diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index 9c958d9e0..f3846bc77 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -344,6 +344,7 @@ namespace BTCPayServer.Services.Invoices InvoiceResponse dto = new InvoiceResponse { Id = Id, + StoreId = StoreId, OrderId = OrderId, PosData = PosData, CurrentTime = DateTimeOffset.UtcNow,