bug fixes and optimizations

This commit is contained in:
Kukks 2019-01-05 19:47:39 +01:00
parent fb6d852827
commit 6e7f1151bc
8 changed files with 190 additions and 127 deletions

View file

@ -2,9 +2,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Controllers; using BTCPayServer.Controllers;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Hubs;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Models.AppViewModels; using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Models.StoreViewModels;
@ -51,7 +54,7 @@ namespace BTCPayServer.Tests
vm.Name = "test"; vm.Name = "test";
vm.SelectedAppType = AppType.Crowdfund.ToString(); vm.SelectedAppType = AppType.Crowdfund.ToString();
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result); var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result);
Assert.Equal(nameof(apps.UpdatePointOfSale), redirectToAction.ActionName); Assert.Equal(nameof(apps.UpdateCrowdfund), redirectToAction.ActionName);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps().Result).Model); var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps().Result).Model);
var appList2 = var appList2 =
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps().Result).Model); Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps().Result).Model);
@ -93,6 +96,7 @@ namespace BTCPayServer.Tests
//Scenario 1: Not Enabled - Not Allowed //Scenario 1: Not Enabled - Not Allowed
var crowdfundViewModel = Assert.IsType<UpdateCrowdfundViewModel>(Assert var crowdfundViewModel = Assert.IsType<UpdateCrowdfundViewModel>(Assert
.IsType<ViewResult>(apps.UpdateCrowdfund(appId).Result).Model); .IsType<ViewResult>(apps.UpdateCrowdfund(appId).Result).Model);
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.Enabled = false; crowdfundViewModel.Enabled = false;
crowdfundViewModel.EndDate = null; crowdfundViewModel.EndDate = null;
@ -115,14 +119,15 @@ namespace BTCPayServer.Tests
RedirectToCheckout = false, RedirectToCheckout = false,
Amount = new decimal(0.01) Amount = new decimal(0.01)
})); }));
Assert.IsType<NotFoundResult>(await publicApps.ViewCrowdfund(appId, string.Empty)); Assert.IsType<ViewResult>(await publicApps.ViewCrowdfund(appId, string.Empty));
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(appId, string.Empty));
//Scenario 3: Enabled But Start Date > Now - Not Allowed //Scenario 3: Enabled But Start Date > Now - Not Allowed
crowdfundViewModel.StartDate= DateTime.Today.AddDays(2); crowdfundViewModel.StartDate= DateTime.Today.AddDays(2);
crowdfundViewModel.Enabled = true; crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result); Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<NotFoundResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{ {
Amount = new decimal(0.01) Amount = new decimal(0.01)
})); }));
@ -134,7 +139,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.Enabled = true; crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result); Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<NotFoundResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{ {
Amount = new decimal(0.01) Amount = new decimal(0.01)
})); }));
@ -148,13 +153,13 @@ namespace BTCPayServer.Tests
crowdfundViewModel.TargetCurrency = "BTC"; crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.EnforceTargetAmount = true; crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result); Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<NotFoundResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{ {
Amount = new decimal(1.01) Amount = new decimal(1.01)
})); }));
//Scenario 6: Allowed //Scenario 6: Allowed
Assert.IsType<NotFoundResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() Assert.IsType<OkObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{ {
Amount = new decimal(0.05) Amount = new decimal(0.05)
})); }));
@ -164,7 +169,7 @@ namespace BTCPayServer.Tests
[Fact] [Fact]
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanComputeCrowdfundModel() public void CanComputeCrowdfundModel()
{ {
using (var tester = ServerTester.Create()) using (var tester = ServerTester.Create())
{ {
@ -200,9 +205,10 @@ namespace BTCPayServer.Tests
Assert.Equal(crowdfundViewModel.EndDate, model.EndDate ); Assert.Equal(crowdfundViewModel.EndDate, model.EndDate );
Assert.Equal(crowdfundViewModel.StartDate, model.StartDate ); Assert.Equal(crowdfundViewModel.StartDate, model.StartDate );
Assert.Equal(crowdfundViewModel.TargetCurrency, model.TargetCurrency ); Assert.Equal(crowdfundViewModel.TargetCurrency, model.TargetCurrency );
Assert.Equal(model.Info.CurrentAmount, 0m); Assert.Equal(0m, model.Info.CurrentAmount );
Assert.Equal(model.Info.CurrentPendingAmount, 0m); Assert.Equal(0m, model.Info.CurrentPendingAmount);
Assert.Equal(model.Info.ProgressPercentage, 0m); Assert.Equal(0m, model.Info.ProgressPercentage);
var invoice = user.BitPay.CreateInvoice(new Invoice() var invoice = user.BitPay.CreateInvoice(new Invoice()
@ -211,8 +217,9 @@ namespace BTCPayServer.Tests
Price = 1m, Price = 1m,
Currency = "BTC", Currency = "BTC",
PosData = "posData", PosData = "posData",
OrderId = "orderId", OrderId = $"{CrowdfundHubStreamer.CrowdfundInvoiceOrderIdPrefix}{appId}",
ItemDesc = "Some description", ItemDesc = "Some description",
TransactionSpeed = "high",
FullNotifications = true FullNotifications = true
}, Facade.Merchant); }, Facade.Merchant);
@ -221,27 +228,18 @@ namespace BTCPayServer.Tests
.IsType<ViewResult>(publicApps.ViewCrowdfund(appId, String.Empty).Result).Model); .IsType<ViewResult>(publicApps.ViewCrowdfund(appId, String.Empty).Result).Model);
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network); 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); 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<ViewCrowdfundViewModel>(Assert
.IsType<ViewResult>(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);
} }

View file

@ -326,7 +326,7 @@ namespace BTCPayServer.Tests
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network); var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network);
tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Satoshis((decimal)invoice.BtcDue.Satoshi * 0.75m)); tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Satoshis((decimal)invoice.BtcDue.Satoshi * 0.75m));
Eventually(() => TestUtils.Eventually(() =>
{ {
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
Assert.Equal("paid", localInvoice.Status); 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 Task.Delay(TimeSpan.FromMilliseconds(1000)); // Give time to listen the new invoices
await tester.SendLightningPaymentAsync(invoice); await tester.SendLightningPaymentAsync(invoice);
await EventuallyAsync(async () => await TestUtils.EventuallyAsync(async () =>
{ {
var localInvoice = await user.BitPay.GetInvoiceAsync(invoice.Id); var localInvoice = await user.BitPay.GetInvoiceAsync(invoice.Id);
Assert.Equal("complete", localInvoice.Status); Assert.Equal("complete", localInvoice.Status);
@ -651,7 +651,7 @@ namespace BTCPayServer.Tests
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Satoshis(10); var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Satoshis(10);
cashCow.SendToAddress(invoiceAddress, firstPayment); cashCow.SendToAddress(invoiceAddress, firstPayment);
Eventually(() => TestUtils.Eventually(() =>
{ {
invoice = acc.BitPay.GetInvoice(invoice.Id); invoice = acc.BitPay.GetInvoice(invoice.Id);
Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid); Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid);
@ -747,7 +747,7 @@ namespace BTCPayServer.Tests
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, user.SupportedNetwork.NBitcoinNetwork); var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, user.SupportedNetwork.NBitcoinNetwork);
Logs.Tester.LogInformation($"The invoice should be paidOver"); Logs.Tester.LogInformation($"The invoice should be paidOver");
Eventually(() => TestUtils.Eventually(() =>
{ {
invoice = user.BitPay.GetInvoice(invoice.Id); invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal(payment1, invoice.BtcPaid); Assert.Equal(payment1, invoice.BtcPaid);
@ -776,7 +776,7 @@ namespace BTCPayServer.Tests
Assert.Equal(tx2, ((NewTransactionEvent)listener.NextEvent(cts.Token)).TransactionData.TransactionHash); Assert.Equal(tx2, ((NewTransactionEvent)listener.NextEvent(cts.Token)).TransactionData.TransactionHash);
} }
Logs.Tester.LogInformation($"The invoice should now not be paidOver anymore"); Logs.Tester.LogInformation($"The invoice should now not be paidOver anymore");
Eventually(() => TestUtils.Eventually(() =>
{ {
invoice = user.BitPay.GetInvoice(invoice.Id); invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal(payment2, invoice.BtcPaid); Assert.Equal(payment2, invoice.BtcPaid);
@ -1023,7 +1023,7 @@ namespace BTCPayServer.Tests
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
var firstPayment = Money.Coins(0.1m); var firstPayment = Money.Coins(0.1m);
cashCow.SendToAddress(invoiceAddress, firstPayment); cashCow.SendToAddress(invoiceAddress, firstPayment);
Eventually(() => TestUtils.Eventually(() =>
{ {
invoice = user.BitPay.GetInvoice(invoice.Id); invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid); 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 Assert.NotEqual(invoice.BtcDue, invoice.CryptoInfo[0].Due); // Should be BTC rate
cashCow.SendToAddress(invoiceAddress, invoice.CryptoInfo[0].Due); cashCow.SendToAddress(invoiceAddress, invoice.CryptoInfo[0].Due);
Eventually(() => TestUtils.Eventually(() =>
{ {
invoice = user.BitPay.GetInvoice(invoice.Id); invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal("paid", invoice.Status); Assert.Equal("paid", invoice.Status);
@ -1142,7 +1142,7 @@ namespace BTCPayServer.Tests
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
var firstPayment = Money.Coins(0.04m); var firstPayment = Money.Coins(0.04m);
cashCow.SendToAddress(invoiceAddress, firstPayment); cashCow.SendToAddress(invoiceAddress, firstPayment);
Eventually(() => TestUtils.Eventually(() =>
{ {
invoice = user.BitPay.GetInvoice(invoice.Id); invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.True(invoice.BtcPaid == firstPayment); Assert.True(invoice.BtcPaid == firstPayment);
@ -1184,7 +1184,7 @@ namespace BTCPayServer.Tests
firstPayment = Money.Coins(0.04m); firstPayment = Money.Coins(0.04m);
cashCow.SendToAddress(invoiceAddress, firstPayment); cashCow.SendToAddress(invoiceAddress, firstPayment);
Logs.Tester.LogInformation("First payment sent to " + invoiceAddress); Logs.Tester.LogInformation("First payment sent to " + invoiceAddress);
Eventually(() => TestUtils.Eventually(() =>
{ {
invoice = user.BitPay.GetInvoice(invoice.Id); invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.True(invoice.BtcPaid == firstPayment); 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.Generate(2); // LTC is not worth a lot, so just to make sure we have money...
cashCow.SendToAddress(invoiceAddress, secondPayment); cashCow.SendToAddress(invoiceAddress, secondPayment);
Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress); Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress);
Eventually(() => TestUtils.Eventually(() =>
{ {
invoice = user.BitPay.GetInvoice(invoice.Id); invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal(Money.Zero, invoice.BtcDue); Assert.Equal(Money.Zero, invoice.BtcDue);
@ -1632,7 +1632,7 @@ donation:
cashCow.SendToAddress(invoiceAddress, 4*networkFee); cashCow.SendToAddress(invoiceAddress, 4*networkFee);
Thread.Sleep(1000); Thread.Sleep(1000);
Eventually(() => TestUtils.Eventually(() =>
{ {
var jsonResultPaid = user.GetController<InvoiceController>().Export("json").GetAwaiter().GetResult(); var jsonResultPaid = user.GetController<InvoiceController>().Export("json").GetAwaiter().GetResult();
var paidresult = Assert.IsType<ContentResult>(jsonResultPaid); var paidresult = Assert.IsType<ContentResult>(jsonResultPaid);
@ -1683,7 +1683,7 @@ donation:
var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Satoshis(10); var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Satoshis(10);
cashCow.SendToAddress(invoiceAddress, firstPayment); cashCow.SendToAddress(invoiceAddress, firstPayment);
Eventually(() => TestUtils.Eventually(() =>
{ {
var exportResultPaid = user.GetController<InvoiceController>().Export("csv").GetAwaiter().GetResult(); var exportResultPaid = user.GetController<InvoiceController>().Export("csv").GetAwaiter().GetResult();
var paidresult = Assert.IsType<ContentResult>(exportResultPaid); var paidresult = Assert.IsType<ContentResult>(exportResultPaid);
@ -1758,7 +1758,7 @@ donation:
Assert.Equal(0, invoice.CryptoInfo[0].TxCount); Assert.Equal(0, invoice.CryptoInfo[0].TxCount);
Assert.True(invoice.MinerFees.ContainsKey("BTC")); Assert.True(invoice.MinerFees.ContainsKey("BTC"));
Assert.Contains(invoice.MinerFees["BTC"].SatoshiPerBytes, new[] { 100.0m, 20.0m }); Assert.Contains(invoice.MinerFees["BTC"].SatoshiPerBytes, new[] { 100.0m, 20.0m });
Eventually(() => TestUtils.Eventually(() =>
{ {
var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery() var textSearchResult = tester.PayTester.InvoiceRepository.GetInvoices(new InvoiceQuery()
{ {
@ -1804,7 +1804,7 @@ donation:
Money secondPayment = Money.Zero; Money secondPayment = Money.Zero;
Eventually(() => TestUtils.Eventually(() =>
{ {
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
Assert.Equal("new", localInvoice.Status); Assert.Equal("new", localInvoice.Status);
@ -1827,7 +1827,7 @@ donation:
cashCow.SendToAddress(invoiceAddress, secondPayment); cashCow.SendToAddress(invoiceAddress, secondPayment);
Eventually(() => TestUtils.Eventually(() =>
{ {
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
Assert.Equal("paid", localInvoice.Status); 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 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); var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
Assert.Equal("confirmed", localInvoice.Status); Assert.Equal("confirmed", localInvoice.Status);
@ -1849,7 +1849,7 @@ donation:
cashCow.Generate(5); //Now should be complete cashCow.Generate(5); //Now should be complete
Eventually(() => TestUtils.Eventually(() =>
{ {
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
Assert.Equal("complete", localInvoice.Status); Assert.Equal("complete", localInvoice.Status);
@ -1871,7 +1871,7 @@ donation:
var txId = cashCow.SendToAddress(invoiceAddress, invoice.BtcDue + Money.Coins(1)); var txId = cashCow.SendToAddress(invoiceAddress, invoice.BtcDue + Money.Coins(1));
Eventually(() => TestUtils.Eventually(() =>
{ {
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
Assert.Equal("paid", localInvoice.Status); Assert.Equal("paid", localInvoice.Status);
@ -1888,7 +1888,7 @@ donation:
cashCow.Generate(1); cashCow.Generate(1);
Eventually(() => TestUtils.Eventually(() =>
{ {
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
Assert.Equal("confirmed", localInvoice.Status); Assert.Equal("confirmed", localInvoice.Status);
@ -2078,36 +2078,39 @@ donation:
return ctx.AddressInvoices.FirstOrDefault(i => i.InvoiceDataId == invoice.Id && i.GetAddress() == h) != null; 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); public static void Eventually(Action act)
while (true)
{ {
try CancellationTokenSource cts = new CancellationTokenSource(20000);
while (true)
{ {
act(); try
break; {
} act();
catch (XunitException) when (!cts.Token.IsCancellationRequested) break;
{ }
cts.Token.WaitHandle.WaitOne(500); catch (XunitException) when (!cts.Token.IsCancellationRequested)
{
cts.Token.WaitHandle.WaitOne(500);
}
} }
} }
}
private async Task EventuallyAsync(Func<Task> act) public static async Task EventuallyAsync(Func<Task> act)
{
CancellationTokenSource cts = new CancellationTokenSource(20000);
while (true)
{ {
try CancellationTokenSource cts = new CancellationTokenSource(20000);
while (true)
{ {
await act(); try
break; {
} await act();
catch (XunitException) when (!cts.Token.IsCancellationRequested) break;
{ }
await Task.Delay(500); catch (XunitException) when (!cts.Token.IsCancellationRequested)
{
await Task.Delay(500);
}
} }
} }
} }

View file

@ -11,6 +11,8 @@ namespace BTCPayServer.Controllers
public class CrowdfundAppUpdated public class CrowdfundAppUpdated
{ {
public string AppId { get; set; } public string AppId { get; set; }
public CrowdfundSettings Settings { get; set; }
public string StoreId { get; set; }
} }
public class CrowdfundSettings public class CrowdfundSettings
@ -83,7 +85,7 @@ namespace BTCPayServer.Controllers
[Route("{appId}/settings/crowdfund")] [Route("{appId}/settings/crowdfund")]
public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm) public async Task<IActionResult> 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"); ModelState.AddModelError(nameof(vm.TargetCurrency), "Invalid currency");
try try
@ -109,7 +111,8 @@ namespace BTCPayServer.Controllers
var app = await GetOwnedApp(appId, AppType.Crowdfund); var app = await GetOwnedApp(appId, AppType.Crowdfund);
if (app == null) if (app == null)
return NotFound(); return NotFound();
app.SetSettings(new CrowdfundSettings()
var newSettings = new CrowdfundSettings()
{ {
Title = vm.Title, Title = vm.Title,
Enabled = vm.Enabled, Enabled = vm.Enabled,
@ -133,11 +136,15 @@ namespace BTCPayServer.Controllers
ResetEvery = Enum.Parse<CrowdfundResetEvery>(vm.ResetEvery), ResetEvery = Enum.Parse<CrowdfundResetEvery>(vm.ResetEvery),
UseInvoiceAmount = vm.UseInvoiceAmount, UseInvoiceAmount = vm.UseInvoiceAmount,
UseAllStoreInvoices = vm.UseAllStoreInvoices UseAllStoreInvoices = vm.UseAllStoreInvoices
}); };
app.SetSettings(newSettings);
await UpdateAppSettings(app); await UpdateAppSettings(app);
_EventAggregator.Publish(new CrowdfundAppUpdated() _EventAggregator.Publish(new CrowdfundAppUpdated()
{ {
AppId = appId AppId = appId,
StoreId = app.StoreDataId,
Settings = newSettings
}); });
StatusMessage = "App updated"; StatusMessage = "App updated";
return RedirectToAction(nameof(ListApps)); return RedirectToAction(nameof(ListApps));

View file

@ -47,7 +47,7 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> ListApps() public async Task<IActionResult> ListApps()
{ {
var apps = await GetAllApps(); var apps = await _AppsHelper.GetAllApps(GetUserId());
return View(new ListAppsViewModel() return View(new ListAppsViewModel()
{ {
Apps = apps Apps = apps
@ -61,7 +61,7 @@ namespace BTCPayServer.Controllers
var appData = await GetOwnedApp(appId); var appData = await GetOwnedApp(appId);
if (appData == null) if (appData == null)
return NotFound(); return NotFound();
if (await DeleteApp(appData)) if (await _AppsHelper.DeleteApp(appData))
StatusMessage = "App removed successfully"; StatusMessage = "App removed successfully";
return RedirectToAction(nameof(ListApps)); return RedirectToAction(nameof(ListApps));
} }
@ -70,7 +70,7 @@ namespace BTCPayServer.Controllers
[Route("create")] [Route("create")]
public async Task<IActionResult> CreateApp() public async Task<IActionResult> CreateApp()
{ {
var stores = await GetOwnedStores(); var stores = await _AppsHelper.GetOwnedStores(GetUserId());
if (stores.Length == 0) if (stores.Length == 0)
{ {
StatusMessage = "Error: You must have created at least one store"; StatusMessage = "Error: You must have created at least one store";
@ -85,7 +85,7 @@ namespace BTCPayServer.Controllers
[Route("create")] [Route("create")]
public async Task<IActionResult> CreateApp(CreateAppViewModel vm) public async Task<IActionResult> CreateApp(CreateAppViewModel vm)
{ {
var stores = await GetOwnedStores(); var stores = await _AppsHelper.GetOwnedStores(GetUserId());
if (stores.Length == 0) if (stores.Length == 0)
{ {
StatusMessage = "Error: You must own at least one store"; StatusMessage = "Error: You must own at least one store";
@ -153,50 +153,7 @@ namespace BTCPayServer.Controllers
return _AppsHelper.GetAppDataIfOwner(GetUserId(), appId, type); return _AppsHelper.GetAppDataIfOwner(GetUserId(), appId, type);
} }
private async Task<StoreData[]> 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<bool> DeleteApp(AppData appData)
{
using (var ctx = _ContextFactory.CreateContext())
{
ctx.Apps.Add(appData);
ctx.Entry<AppData>(appData).State = EntityState.Deleted;
return await ctx.SaveChangesAsync() == 1;
}
}
private async Task<ListAppsViewModel.ListAppViewModel[]> 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() private string GetUserId()
{ {
return _UserManager.GetUserId(User); return _UserManager.GetUserId(User);

View file

@ -151,7 +151,7 @@ namespace BTCPayServer.Controllers
price = request.Amount; 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)))) (info.TargetAmount - (info.Info.CurrentAmount + info.Info.CurrentPendingAmount))))
{ {
return NotFound("Contribution Amount is more than is currently allowed."); return NotFound("Contribution Amount is more than is currently allowed.");
@ -264,6 +264,49 @@ namespace BTCPayServer.Controllers
_Currencies = currencies; _Currencies = currencies;
} }
public async Task<StoreData[]> 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<bool> DeleteApp(AppData appData)
{
using (var ctx = _ContextFactory.CreateContext())
{
ctx.Apps.Add(appData);
ctx.Entry<AppData>(appData).State = EntityState.Deleted;
return await ctx.SaveChangesAsync() == 1;
}
}
public async Task<ListAppsViewModel.ListAppViewModel[]> 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<AppData> GetApp(string appId, AppType appType, bool includeStore = false) public async Task<AppData> GetApp(string appId, AppType appType, bool includeStore = false)
{ {

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -15,6 +16,8 @@ using BTCPayServer.Services.Rates;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using NBitcoin;
using YamlDotNet.Core;
namespace BTCPayServer.Hubs namespace BTCPayServer.Hubs
{ {
@ -28,6 +31,9 @@ namespace BTCPayServer.Hubs
private readonly RateFetcher _RateFetcher; private readonly RateFetcher _RateFetcher;
private readonly BTCPayNetworkProvider _BtcPayNetworkProvider; private readonly BTCPayNetworkProvider _BtcPayNetworkProvider;
private readonly InvoiceRepository _InvoiceRepository; private readonly InvoiceRepository _InvoiceRepository;
private readonly ConcurrentDictionary<string,(string appId, bool useAllStoreInvoices,bool useInvoiceAmount)> _QuickAppInvoiceLookup =
new ConcurrentDictionary<string, (string appId, bool useAllStoreInvoices, bool useInvoiceAmount)>();
public CrowdfundHubStreamer(EventAggregator eventAggregator, public CrowdfundHubStreamer(EventAggregator eventAggregator,
IHubContext<CrowdfundHub> hubContext, IHubContext<CrowdfundHub> hubContext,
IMemoryCache memoryCache, IMemoryCache memoryCache,
@ -43,9 +49,36 @@ namespace BTCPayServer.Hubs
_RateFetcher = rateFetcher; _RateFetcher = rateFetcher;
_BtcPayNetworkProvider = btcPayNetworkProvider; _BtcPayNetworkProvider = btcPayNetworkProvider;
_InvoiceRepository = invoiceRepository; _InvoiceRepository = invoiceRepository;
#pragma warning disable 4014
InitLookup();
#pragma warning restore 4014
SubscribeToEvents(); SubscribeToEvents();
} }
private async Task InitLookup()
{
var apps = await _AppsHelper.GetAllApps(null, true);
apps = apps.Where(model => Enum.Parse<AppType>(model.AppType) == AppType.Crowdfund).ToArray();
var tasks = new List<Task>();
tasks.AddRange(apps.Select(app => Task.Run(async () =>
{
var fullApp = await _AppsHelper.GetApp(app.Id, AppType.Crowdfund, false);
var settings = fullApp.GetSettings<AppsController.CrowdfundSettings>();
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<ViewCrowdfundViewModel> GetCrowdfundInfo(string appId) public Task<ViewCrowdfundViewModel> GetCrowdfundInfo(string appId)
{ {
return _MemoryCache.GetOrCreateAsync(GetCacheKey(appId), async entry => return _MemoryCache.GetOrCreateAsync(GetCacheKey(appId), async entry =>
@ -80,6 +113,7 @@ namespace BTCPayServer.Hubs
_EventAggregator.Subscribe<InvoiceEvent>(OnInvoiceEvent); _EventAggregator.Subscribe<InvoiceEvent>(OnInvoiceEvent);
_EventAggregator.Subscribe<AppsController.CrowdfundAppUpdated>(updated => _EventAggregator.Subscribe<AppsController.CrowdfundAppUpdated>(updated =>
{ {
UpdateLookup(updated.AppId, updated.StoreId, updated.Settings);
InvalidateCacheForApp(updated.AppId); InvalidateCacheForApp(updated.AppId);
}); });
} }
@ -95,12 +129,20 @@ namespace BTCPayServer.Hubs
{ {
return; 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) switch (invoiceEvent.Name)
{ {
case InvoiceEvent.ReceivedPayment: case InvoiceEvent.ReceivedPayment:
var data = invoiceEvent.Payment.GetCryptoPaymentData(); 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(), data.GetValue(),
invoiceEvent.Payment.GetCryptoCode(), invoiceEvent.Payment.GetCryptoCode(),
@ -108,10 +150,16 @@ namespace BTCPayServer.Hubs
invoiceEvent.Payment.GetPaymentMethodId().PaymentType) invoiceEvent.Payment.GetPaymentMethodId().PaymentType)
} ); } );
InvalidateCacheForApp(appId); InvalidateCacheForApp(quickLookup.appId);
break;
case InvoiceEvent.Created:
if (quickLookup.useInvoiceAmount)
{
InvalidateCacheForApp(quickLookup.appId);
}
break; break;
case InvoiceEvent.Completed: case InvoiceEvent.Completed:
InvalidateCacheForApp(appId); InvalidateCacheForApp(quickLookup.appId);
break; break;
} }
} }
@ -123,7 +171,7 @@ namespace BTCPayServer.Hubs
GetCrowdfundInfo(appId).ContinueWith(task => GetCrowdfundInfo(appId).ContinueWith(task =>
{ {
_HubContext.Clients.Group(appId).SendCoreAsync(CrowdfundHub.InfoUpdated, new object[]{ task.Result} ); _HubContext.Clients.Group(appId).SendCoreAsync(CrowdfundHub.InfoUpdated, new object[]{ task.Result} );
}, TaskScheduler.Default); }, TaskScheduler.Current);
} }

View file

@ -40,6 +40,12 @@ namespace BTCPayServer.Models
//{"facade":"pos/invoice","data":{,}} //{"facade":"pos/invoice","data":{,}}
public class InvoiceResponse public class InvoiceResponse
{ {
[JsonIgnore]
public string StoreId
{
get; set;
}
//"url":"https://test.bitpay.com/invoice?id=9saCHtp1zyPcNoi3rDdBu8" //"url":"https://test.bitpay.com/invoice?id=9saCHtp1zyPcNoi3rDdBu8"
[JsonProperty("url")] [JsonProperty("url")]
public string Url public string Url

View file

@ -344,6 +344,7 @@ namespace BTCPayServer.Services.Invoices
InvoiceResponse dto = new InvoiceResponse InvoiceResponse dto = new InvoiceResponse
{ {
Id = Id, Id = Id,
StoreId = StoreId,
OrderId = OrderId, OrderId = OrderId,
PosData = PosData, PosData = PosData,
CurrentTime = DateTimeOffset.UtcNow, CurrentTime = DateTimeOffset.UtcNow,