Warning if not using 'simple using'

This commit is contained in:
nicolas.dorier 2022-01-14 17:50:29 +09:00
parent c6a7e90c1a
commit 50d4b55f73
No known key found for this signature in database
GPG Key ID: 6618763EF09186FE
40 changed files with 5355 additions and 5732 deletions

View File

@ -122,6 +122,7 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false csharp_space_between_square_brackets = false
csharp_style_prefer_null_check_over_type_check = true:warning csharp_style_prefer_null_check_over_type_check = true:warning
csharp_prefer_simple_using_statement = true:warning
# C++ Files # C++ Files

View File

@ -21,12 +21,10 @@ namespace BTCPayServer
public static string Unzip(byte[] bytes) public static string Unzip(byte[] bytes)
{ {
MemoryStream ms = new MemoryStream(bytes); MemoryStream ms = new MemoryStream(bytes);
using (GZipStream gzip = new GZipStream(ms, CompressionMode.Decompress)) using GZipStream gzip = new GZipStream(ms, CompressionMode.Decompress);
{ StreamReader reader = new StreamReader(gzip, Encoding.UTF8);
StreamReader reader = new StreamReader(gzip, Encoding.UTF8); var unzipped = reader.ReadToEnd();
var unzipped = reader.ReadToEnd(); return unzipped;
return unzipped;
}
} }
} }
} }

View File

@ -35,166 +35,164 @@ namespace BTCPayServer.Tests
//as a user through your profile //as a user through your profile
//as an external application requesting an api key from a user //as an external application requesting an api key from a user
using (var s = CreateSeleniumTester()) using var s = CreateSeleniumTester();
await s.StartAsync();
var tester = s.Server;
var user = tester.NewAccount();
await user.GrantAccessAsync();
await user.MakeAdmin(false);
s.GoToLogin();
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.GoToProfile(ManageNavPages.APIKeys);
s.Driver.FindElement(By.Id("AddApiKey")).Click();
//not an admin, so this permission should not show
Assert.DoesNotContain("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
await user.MakeAdmin();
s.Logout();
s.GoToLogin();
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.GoToProfile(ManageNavPages.APIKeys);
s.Driver.FindElement(By.Id("AddApiKey")).Click();
Assert.Contains("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
//server management should show now
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true);
s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true);
s.Driver.SetCheckbox(By.Id("btcpay.user.canviewprofile"), true);
s.Driver.FindElement(By.Id("Generate")).Click();
var superApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
//this api key has access to everything
await TestApiAgainstAccessToken(superApiKey, tester, user, Policies.CanModifyServerSettings, Policies.CanModifyStoreSettings, Policies.CanViewProfile);
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true);
s.Driver.FindElement(By.Id("Generate")).Click();
var serverOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user,
Policies.CanModifyServerSettings);
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true);
s.Driver.FindElement(By.Id("Generate")).Click();
var allStoreOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user,
Policies.CanModifyStoreSettings);
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.Driver.FindElement(By.CssSelector("button[value='btcpay.store.canmodifystoresettings:change-store-mode']")).Click();
//there should be a store already by default in the dropdown
var src = s.Driver.PageSource;
var getPermissionValueIndex =
s.Driver.FindElement(By.CssSelector("input[value='btcpay.store.canmodifystoresettings']"))
.GetAttribute("name")
.Replace(".Permission", ".SpecificStores[0]");
var dropdown = s.Driver.FindElement(By.Name(getPermissionValueIndex));
var option = dropdown.FindElement(By.TagName("option"));
var storeId = option.GetAttribute("value");
option.Click();
s.Driver.FindElement(By.Id("Generate")).Click();
var selectiveStoreApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(selectiveStoreApiKey, tester, user,
Permission.Create(Policies.CanModifyStoreSettings, storeId).ToString());
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.Driver.FindElement(By.Id("Generate")).Click();
var noPermissionsApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(noPermissionsApiKey, tester, user);
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
{ {
await s.StartAsync(); await TestApiAgainstAccessToken<bool>("incorrect key", $"{TestApiPath}/me/id",
var tester = s.Server; tester.PayTester.HttpClient);
});
var user = tester.NewAccount(); //let's test the authorized screen now
await user.GrantAccessAsync(); //options for authorize are:
await user.MakeAdmin(false); //applicationName
s.GoToLogin(); //redirect
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); //permissions
s.GoToProfile(ManageNavPages.APIKeys); //strict
s.Driver.FindElement(By.Id("AddApiKey")).Click(); //selectiveStores
//redirect
//appidentifier
var appidentifier = "testapp";
var callbackUrl = s.ServerUri + "postredirect-callback-test";
var authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, applicationDetails: (appidentifier, new Uri(callbackUrl))).ToString();
s.Driver.Navigate().GoToUrl(authUrl);
Assert.Contains(appidentifier, s.Driver.PageSource);
Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant());
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("value").ToLowerInvariant());
Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant());
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant());
Assert.DoesNotContain("change-store-mode", s.Driver.PageSource);
s.Driver.FindElement(By.Id("consent-yes")).Click();
Assert.Equal(callbackUrl, s.Driver.Url);
//not an admin, so this permission should not show var apiKeyRepo = s.Server.PayTester.GetService<APIKeyRepository>();
Assert.DoesNotContain("btcpay.server.canmodifyserversettings", s.Driver.PageSource); var accessToken = GetAccessTokenFromCallbackResult(s.Driver);
await user.MakeAdmin();
s.Logout();
s.GoToLogin();
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.GoToProfile(ManageNavPages.APIKeys);
s.Driver.FindElement(By.Id("AddApiKey")).Click();
Assert.Contains("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
//server management should show now await TestApiAgainstAccessToken(accessToken, tester, user,
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true); (await apiKeyRepo.GetKey(accessToken)).GetBlob().Permissions);
s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true);
s.Driver.SetCheckbox(By.Id("btcpay.user.canviewprofile"), true);
s.Driver.FindElement(By.Id("Generate")).Click();
var superApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
//this api key has access to everything authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
await TestApiAgainstAccessToken(superApiKey, tester, user, Policies.CanModifyServerSettings, Policies.CanModifyStoreSettings, Policies.CanViewProfile); new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, applicationDetails: (null, new Uri(callbackUrl))).ToString();
s.Driver.Navigate().GoToUrl(authUrl);
Assert.DoesNotContain("kukksappname", s.Driver.PageSource);
s.Driver.FindElement(By.Id("AddApiKey")).Click(); Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant());
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true); Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("value").ToLowerInvariant());
s.Driver.FindElement(By.Id("Generate")).Click(); Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant());
var serverOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text; Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant());
await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user,
Policies.CanModifyServerSettings);
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), false);
Assert.Contains("change-store-mode", s.Driver.PageSource);
s.Driver.FindElement(By.Id("consent-yes")).Click();
Assert.Equal(callbackUrl, s.Driver.Url);
s.Driver.FindElement(By.Id("AddApiKey")).Click(); accessToken = GetAccessTokenFromCallbackResult(s.Driver);
s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true); await TestApiAgainstAccessToken(accessToken, tester, user,
s.Driver.FindElement(By.Id("Generate")).Click(); (await apiKeyRepo.GetKey(accessToken)).GetBlob().Permissions);
var allStoreOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user,
Policies.CanModifyStoreSettings);
s.Driver.FindElement(By.Id("AddApiKey")).Click(); //let's test the app identifier system
s.Driver.FindElement(By.CssSelector("button[value='btcpay.store.canmodifystoresettings:change-store-mode']")).Click(); authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
//there should be a store already by default in the dropdown new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, (appidentifier, new Uri(callbackUrl))).ToString();
var src = s.Driver.PageSource;
var getPermissionValueIndex =
s.Driver.FindElement(By.CssSelector("input[value='btcpay.store.canmodifystoresettings']"))
.GetAttribute("name")
.Replace(".Permission", ".SpecificStores[0]");
var dropdown = s.Driver.FindElement(By.Name(getPermissionValueIndex));
var option = dropdown.FindElement(By.TagName("option"));
var storeId = option.GetAttribute("value");
option.Click();
s.Driver.FindElement(By.Id("Generate")).Click();
var selectiveStoreApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(selectiveStoreApiKey, tester, user,
Permission.Create(Policies.CanModifyStoreSettings, storeId).ToString());
s.Driver.FindElement(By.Id("AddApiKey")).Click(); //if it's the same, go to the confirm page
s.Driver.FindElement(By.Id("Generate")).Click(); s.Driver.Navigate().GoToUrl(authUrl);
var noPermissionsApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text; s.Driver.FindElement(By.Id("continue")).Click();
await TestApiAgainstAccessToken(noPermissionsApiKey, tester, user); Assert.Equal(callbackUrl, s.Driver.Url);
await Assert.ThrowsAnyAsync<HttpRequestException>(async () => //same app but different redirect = nono
{ authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
await TestApiAgainstAccessToken<bool>("incorrect key", $"{TestApiPath}/me/id", new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, (appidentifier, new Uri("https://international.local/callback"))).ToString();
tester.PayTester.HttpClient);
});
//let's test the authorized screen now s.Driver.Navigate().GoToUrl(authUrl);
//options for authorize are: Assert.False(s.Driver.Url.StartsWith("https://international.com/callback"));
//applicationName
//redirect
//permissions
//strict
//selectiveStores
//redirect
//appidentifier
var appidentifier = "testapp";
var callbackUrl = s.ServerUri + "postredirect-callback-test";
var authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, applicationDetails: (appidentifier, new Uri(callbackUrl))).ToString();
s.Driver.Navigate().GoToUrl(authUrl);
Assert.Contains(appidentifier, s.Driver.PageSource);
Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant());
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("value").ToLowerInvariant());
Assert.Equal("hidden", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant());
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant());
Assert.DoesNotContain("change-store-mode", s.Driver.PageSource);
s.Driver.FindElement(By.Id("consent-yes")).Click();
Assert.Equal(callbackUrl, s.Driver.Url);
var apiKeyRepo = s.Server.PayTester.GetService<APIKeyRepository>(); // Make sure we can check all permissions when not an admin
var accessToken = GetAccessTokenFromCallbackResult(s.Driver); await user.MakeAdmin(false);
s.Logout();
await TestApiAgainstAccessToken(accessToken, tester, user, s.GoToLogin();
(await apiKeyRepo.GetKey(accessToken)).GetBlob().Permissions); s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.GoToProfile(ManageNavPages.APIKeys);
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri, s.Driver.FindElement(By.Id("AddApiKey")).Click();
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, applicationDetails: (null, new Uri(callbackUrl))).ToString(); int checkedPermissionCount = 0;
foreach (var checkbox in s.Driver.FindElements(By.ClassName("form-check-input")))
s.Driver.Navigate().GoToUrl(authUrl); {
Assert.DoesNotContain("kukksappname", s.Driver.PageSource); checkedPermissionCount++;
checkbox.Click();
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("type").ToLowerInvariant());
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.store.canmodifystoresettings")).GetAttribute("value").ToLowerInvariant());
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant());
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant());
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), false);
Assert.Contains("change-store-mode", s.Driver.PageSource);
s.Driver.FindElement(By.Id("consent-yes")).Click();
Assert.Equal(callbackUrl, s.Driver.Url);
accessToken = GetAccessTokenFromCallbackResult(s.Driver);
await TestApiAgainstAccessToken(accessToken, tester, user,
(await apiKeyRepo.GetKey(accessToken)).GetBlob().Permissions);
//let's test the app identifier system
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, (appidentifier, new Uri(callbackUrl))).ToString();
//if it's the same, go to the confirm page
s.Driver.Navigate().GoToUrl(authUrl);
s.Driver.FindElement(By.Id("continue")).Click();
Assert.Equal(callbackUrl, s.Driver.Url);
//same app but different redirect = nono
authUrl = BTCPayServerClient.GenerateAuthorizeUri(s.ServerUri,
new[] { Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings }, false, true, (appidentifier, new Uri("https://international.local/callback"))).ToString();
s.Driver.Navigate().GoToUrl(authUrl);
Assert.False(s.Driver.Url.StartsWith("https://international.com/callback"));
// Make sure we can check all permissions when not an admin
await user.MakeAdmin(false);
s.Logout();
s.GoToLogin();
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.GoToProfile(ManageNavPages.APIKeys);
s.Driver.FindElement(By.Id("AddApiKey")).Click();
int checkedPermissionCount = 0;
foreach (var checkbox in s.Driver.FindElements(By.ClassName("form-check-input")))
{
checkedPermissionCount++;
checkbox.Click();
}
s.Driver.FindElement(By.Id("Generate")).Click();
var allAPIKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(allAPIKey, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
Assert.Equal(checkedPermissionCount, apikeydata.Permissions.Length);
} }
s.Driver.FindElement(By.Id("Generate")).Click();
var allAPIKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(allAPIKey, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
Assert.Equal(checkedPermissionCount, apikeydata.Permissions.Length);
} }
async Task TestApiAgainstAccessToken(string accessToken, ServerTester tester, TestAccount testAccount, async Task TestApiAgainstAccessToken(string accessToken, ServerTester tester, TestAccount testAccount,

View File

@ -243,11 +243,9 @@ namespace BTCPayServer.Tests
private async Task WaitSiteIsOperational() private async Task WaitSiteIsOperational()
{ {
_ = HttpClient.GetAsync("/").ConfigureAwait(false); _ = HttpClient.GetAsync("/").ConfigureAwait(false);
using (var cts = new CancellationTokenSource(20_000)) using var cts = new CancellationTokenSource(20_000);
{ var synching = WaitIsFullySynched(cts.Token);
var synching = WaitIsFullySynched(cts.Token); await Task.WhenAll(synching).ConfigureAwait(false);
await Task.WhenAll(synching).ConfigureAwait(false);
}
// Opportunistic call to wake up view compilation in debug mode, we don't need to await. // Opportunistic call to wake up view compilation in debug mode, we don't need to await.
} }

View File

@ -23,227 +23,215 @@ namespace BTCPayServer.Tests
public async Task CanHandleRefundEmailForm() public async Task CanHandleRefundEmailForm()
{ {
using (var s = CreateSeleniumTester()) using var s = CreateSeleniumTester();
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
s.CreateNewStore();
s.AddDerivationScheme("BTC");
s.GoToStore(StoreNavPages.CheckoutAppearance);
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
s.Driver.FindElement(By.Name("command")).Click();
var emailAlreadyThereInvoiceId = s.CreateInvoice(100, "USD", "a@g.com");
s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.GoToHome();
s.CreateInvoice();
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
Assert.True(s.Driver.FindElement(By.Id("emailAddressFormInput")).Displayed);
s.Driver.FindElement(By.Id("emailAddressFormInput")).SendKeys("xxx");
s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"))
.Click();
var formInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
Assert.True(formInput.Displayed);
Assert.Contains("form-input-invalid", formInput.GetAttribute("class"));
formInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
formInput.SendKeys("@g.com");
var actionButton = s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"));
actionButton.Click();
try // Sometimes the click only take the focus, without actually really clicking on it...
{ {
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
s.CreateNewStore();
s.AddDerivationScheme("BTC");
s.GoToStore(StoreNavPages.CheckoutAppearance);
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
s.Driver.FindElement(By.Name("command")).Click();
var emailAlreadyThereInvoiceId = s.CreateInvoice(100, "USD", "a@g.com");
s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.GoToHome();
s.CreateInvoice();
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
Assert.True(s.Driver.FindElement(By.Id("emailAddressFormInput")).Displayed);
s.Driver.FindElement(By.Id("emailAddressFormInput")).SendKeys("xxx");
s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"))
.Click();
var formInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
Assert.True(formInput.Displayed);
Assert.Contains("form-input-invalid", formInput.GetAttribute("class"));
formInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
formInput.SendKeys("@g.com");
var actionButton = s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"));
actionButton.Click(); actionButton.Click();
try // Sometimes the click only take the focus, without actually really clicking on it...
{
actionButton.Click();
}
catch { }
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.Driver.Navigate().Refresh();
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
} }
catch { }
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.Driver.Navigate().Refresh();
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
} }
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]
public async Task CanHandleRefundEmailForm2() public async Task CanHandleRefundEmailForm2()
{ {
using (var s = CreateSeleniumTester()) using var s = CreateSeleniumTester();
// Prepare user account and store
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
s.CreateNewStore();
s.AddDerivationScheme("BTC");
// Now create an invoice that requires a refund email
var invoice = s.CreateInvoice(100, "USD", "", null, true);
s.GoToInvoiceCheckout(invoice);
var emailInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
Assert.True(emailInput.Displayed);
emailInput.SendKeys("a@g.com");
var actionButton = s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"));
actionButton.Click();
try // Sometimes the click only take the focus, without actually really clicking on it...
{ {
// Prepare user account and store
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
s.CreateNewStore();
s.AddDerivationScheme("BTC");
// Now create an invoice that requires a refund email
var invoice = s.CreateInvoice(100, "USD", "", null, true);
s.GoToInvoiceCheckout(invoice);
var emailInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
Assert.True(emailInput.Displayed);
emailInput.SendKeys("a@g.com");
var actionButton = s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"));
actionButton.Click(); actionButton.Click();
try // Sometimes the click only take the focus, without actually really clicking on it...
{
actionButton.Click();
}
catch { }
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.Driver.Navigate().Refresh();
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.GoToHome();
// Now create an invoice that doesn't require a refund email
s.CreateInvoice(100, "USD", "", null, false);
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.Driver.Navigate().Refresh();
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.GoToHome();
// Now create an invoice that requires refund email but already has one set, email input shouldn't show up
s.CreateInvoice(100, "USD", "a@g.com", null, true);
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.Driver.Navigate().Refresh();
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
} }
catch { }
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.Driver.Navigate().Refresh();
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.GoToHome();
// Now create an invoice that doesn't require a refund email
s.CreateInvoice(100, "USD", "", null, false);
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.Driver.Navigate().Refresh();
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.GoToHome();
// Now create an invoice that requires refund email but already has one set, email input shouldn't show up
s.CreateInvoice(100, "USD", "a@g.com", null, true);
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.Driver.Navigate().Refresh();
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
} }
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]
public async Task CanUseLanguageDropdown() public async Task CanUseLanguageDropdown()
{ {
using (var s = CreateSeleniumTester()) using var s = CreateSeleniumTester();
{ await s.StartAsync();
await s.StartAsync(); s.GoToRegister();
s.GoToRegister(); s.RegisterNewUser();
s.RegisterNewUser(); s.CreateNewStore();
s.CreateNewStore(); s.AddDerivationScheme("BTC");
s.AddDerivationScheme("BTC");
var invoiceId = s.CreateInvoice(); var invoiceId = s.CreateInvoice();
s.GoToInvoiceCheckout(invoiceId); s.GoToInvoiceCheckout(invoiceId);
Assert.True(s.Driver.FindElement(By.Id("DefaultLang")).FindElements(By.TagName("option")).Count > 1); Assert.True(s.Driver.FindElement(By.Id("DefaultLang")).FindElements(By.TagName("option")).Count > 1);
var payWithTextEnglish = s.Driver.FindElement(By.Id("pay-with-text")).Text; var payWithTextEnglish = s.Driver.FindElement(By.Id("pay-with-text")).Text;
var prettyDropdown = s.Driver.FindElement(By.Id("prettydropdown-DefaultLang")); var prettyDropdown = s.Driver.FindElement(By.Id("prettydropdown-DefaultLang"));
prettyDropdown.Click(); prettyDropdown.Click();
await Task.Delay(200); await Task.Delay(200);
prettyDropdown.FindElement(By.CssSelector("[data-value=\"da-DK\"]")).Click(); prettyDropdown.FindElement(By.CssSelector("[data-value=\"da-DK\"]")).Click();
await Task.Delay(1000); await Task.Delay(1000);
Assert.NotEqual(payWithTextEnglish, s.Driver.FindElement(By.Id("pay-with-text")).Text); Assert.NotEqual(payWithTextEnglish, s.Driver.FindElement(By.Id("pay-with-text")).Text);
s.Driver.Navigate().GoToUrl(s.Driver.Url + "?lang=da-DK"); s.Driver.Navigate().GoToUrl(s.Driver.Url + "?lang=da-DK");
Assert.NotEqual(payWithTextEnglish, s.Driver.FindElement(By.Id("pay-with-text")).Text); Assert.NotEqual(payWithTextEnglish, s.Driver.FindElement(By.Id("pay-with-text")).Text);
s.Driver.Quit(); s.Driver.Quit();
}
} }
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]
[Trait("Lightning", "Lightning")] [Trait("Lightning", "Lightning")]
public async Task CanSetDefaultPaymentMethod() public async Task CanSetDefaultPaymentMethod()
{ {
using (var s = CreateSeleniumTester()) using var s = CreateSeleniumTester();
{ s.Server.ActivateLightning();
s.Server.ActivateLightning(); await s.StartAsync();
await s.StartAsync(); s.GoToRegister();
s.GoToRegister(); s.RegisterNewUser(true);
s.RegisterNewUser(true); s.CreateNewStore();
s.CreateNewStore(); s.AddLightningNode();
s.AddLightningNode(); s.AddDerivationScheme("BTC");
s.AddDerivationScheme("BTC");
var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike"); var invoiceId = s.CreateInvoice(defaultPaymentMethod: "BTC_LightningLike");
s.GoToInvoiceCheckout(invoiceId); s.GoToInvoiceCheckout(invoiceId);
Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text); Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
s.Driver.Quit(); s.Driver.Quit();
}
} }
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]
[Trait("Lightning", "Lightning")] [Trait("Lightning", "Lightning")]
public async Task CanUseLightningSatsFeature() public async Task CanUseLightningSatsFeature()
{ {
using (var s = CreateSeleniumTester()) using var s = CreateSeleniumTester();
{ s.Server.ActivateLightning();
s.Server.ActivateLightning(); await s.StartAsync();
await s.StartAsync(); s.GoToRegister();
s.GoToRegister(); s.RegisterNewUser(true);
s.RegisterNewUser(true); s.CreateNewStore();
s.CreateNewStore(); s.AddLightningNode();
s.AddLightningNode(); s.GoToStore();
s.GoToStore(); s.Driver.FindElement(By.Id("Modify-LightningBTC")).Click();
s.Driver.FindElement(By.Id("Modify-LightningBTC")).Click(); s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true);
s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true); s.Driver.FindElement(By.Id("save")).Click();
s.Driver.FindElement(By.Id("save")).Click(); Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
var invoiceId = s.CreateInvoice(10, "USD", "a@g.com"); var invoiceId = s.CreateInvoice(10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId); s.GoToInvoiceCheckout(invoiceId);
Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text); Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
}
} }
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]
public async Task CanUseJSModal() public async Task CanUseJSModal()
{ {
using (var s = CreateSeleniumTester()) using var s = CreateSeleniumTester();
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
s.CreateNewStore();
s.GoToStore();
s.AddDerivationScheme();
var invoiceId = s.CreateInvoice(0.001m, "BTC", "a@x.com");
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
s.Driver.Navigate()
.GoToUrl(new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}"));
TestUtils.Eventually(() =>
{ {
await s.StartAsync(); Assert.True(s.Driver.FindElement(By.Name("btcpay")).Displayed);
s.GoToRegister(); });
s.RegisterNewUser(); await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
s.CreateNewStore(); .GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike))
s.GoToStore(); .GetPaymentMethodDetails().GetPaymentDestination(), Network.RegTest),
s.AddDerivationScheme(); new Money(0.001m, MoneyUnit.BTC));
var invoiceId = s.CreateInvoice(0.001m, "BTC", "a@x.com");
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
s.Driver.Navigate()
.GoToUrl(new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}"));
TestUtils.Eventually(() =>
{
Assert.True(s.Driver.FindElement(By.Name("btcpay")).Displayed);
});
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike))
.GetPaymentMethodDetails().GetPaymentDestination(), Network.RegTest),
new Money(0.001m, MoneyUnit.BTC));
IWebElement closebutton = null; IWebElement closebutton = null;
TestUtils.Eventually(() => TestUtils.Eventually(() =>
{ {
var frameElement = s.Driver.FindElement(By.Name("btcpay")); var frameElement = s.Driver.FindElement(By.Name("btcpay"));
var iframe = s.Driver.SwitchTo().Frame(frameElement); var iframe = s.Driver.SwitchTo().Frame(frameElement);
closebutton = iframe.FindElement(By.ClassName("close-action")); closebutton = iframe.FindElement(By.ClassName("close-action"));
Assert.True(closebutton.Displayed); Assert.True(closebutton.Displayed);
}); });
closebutton.Click(); closebutton.Click();
s.Driver.AssertElementNotFound(By.Name("btcpay")); s.Driver.AssertElementNotFound(By.Name("btcpay"));
Assert.Equal(s.Driver.Url, Assert.Equal(s.Driver.Url,
new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}").ToString()); new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}").ToString());
}
} }
} }
} }

View File

@ -26,256 +26,250 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanCreateAndDeleteCrowdfundApp() public async Task CanCreateAndDeleteCrowdfundApp()
{ {
using (var tester = CreateServerTester()) using var tester = CreateServerTester();
{ await tester.StartAsync();
await tester.StartAsync(); var user = tester.NewAccount();
var user = tester.NewAccount(); await user.GrantAccessAsync();
await user.GrantAccessAsync(); var user2 = tester.NewAccount();
var user2 = tester.NewAccount(); await user2.GrantAccessAsync();
await user2.GrantAccessAsync(); var apps = user.GetController<UIAppsController>();
var apps = user.GetController<UIAppsController>(); var apps2 = user2.GetController<UIAppsController>();
var apps2 = user2.GetController<UIAppsController>(); var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model); Assert.NotNull(vm.SelectedAppType);
Assert.NotNull(vm.SelectedAppType); Assert.Null(vm.AppName);
Assert.Null(vm.AppName); vm.AppName = "test";
vm.AppName = "test"; vm.SelectedAppType = AppType.Crowdfund.ToString();
vm.SelectedAppType = AppType.Crowdfund.ToString(); var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result); Assert.Equal(nameof(apps.UpdateCrowdfund), redirectToAction.ActionName);
Assert.Equal(nameof(apps.UpdateCrowdfund), redirectToAction.ActionName); var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model); var app = appList.Apps[0];
var app = appList.Apps[0]; apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName });
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName }); var appList2 =
var appList2 = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model);
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model); Assert.Single(appList.Apps);
Assert.Single(appList.Apps); Assert.Empty(appList2.Apps);
Assert.Empty(appList2.Apps); Assert.Equal("test", appList.Apps[0].AppName);
Assert.Equal("test", appList.Apps[0].AppName); Assert.Equal(apps.CreatedAppId, appList.Apps[0].Id);
Assert.Equal(apps.CreatedAppId, appList.Apps[0].Id); Assert.True(appList.Apps[0].IsOwner);
Assert.True(appList.Apps[0].IsOwner); Assert.Equal(user.StoreId, appList.Apps[0].StoreId);
Assert.Equal(user.StoreId, appList.Apps[0].StoreId); Assert.IsType<NotFoundResult>(apps2.DeleteApp(appList.Apps[0].Id));
Assert.IsType<NotFoundResult>(apps2.DeleteApp(appList.Apps[0].Id)); Assert.IsType<ViewResult>(apps.DeleteApp(appList.Apps[0].Id));
Assert.IsType<ViewResult>(apps.DeleteApp(appList.Apps[0].Id)); redirectToAction = Assert.IsType<RedirectToActionResult>(apps.DeleteAppPost(appList.Apps[0].Id).Result);
redirectToAction = Assert.IsType<RedirectToActionResult>(apps.DeleteAppPost(appList.Apps[0].Id).Result); Assert.Equal(nameof(apps.ListApps), redirectToAction.ActionName);
Assert.Equal(nameof(apps.ListApps), redirectToAction.ActionName); appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model); Assert.Empty(appList.Apps);
Assert.Empty(appList.Apps);
}
} }
[Fact(Timeout = LongRunningTestTimeout)] [Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanContributeOnlyWhenAllowed() public async Task CanContributeOnlyWhenAllowed()
{ {
using (var tester = CreateServerTester()) using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
var apps = user.GetController<UIAppsController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
vm.AppName = "test";
vm.SelectedAppType = AppType.Crowdfund.ToString();
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName });
//Scenario 1: Not Enabled - Not Allowed
var crowdfundViewModel = Assert.IsType<UpdateCrowdfundViewModel>(Assert
.IsType<ViewResult>(apps.UpdateCrowdfund(app.Id)).Model);
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.Enabled = false;
crowdfundViewModel.EndDate = null;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
var publicApps = user.GetController<UIAppsPublicController>();
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{ {
await tester.StartAsync(); Amount = new decimal(0.01)
var user = tester.NewAccount(); }, default));
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
var apps = user.GetController<UIAppsController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
vm.AppName = "test";
vm.SelectedAppType = AppType.Crowdfund.ToString();
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName });
//Scenario 1: Not Enabled - Not Allowed Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty));
var crowdfundViewModel = Assert.IsType<UpdateCrowdfundViewModel>(Assert
.IsType<ViewResult>(apps.UpdateCrowdfund(app.Id)).Model);
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.Enabled = false;
crowdfundViewModel.EndDate = null;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); //Scenario 2: Not Enabled But Admin - Allowed
Assert.IsType<OkObjectResult>(await publicApps.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
RedirectToCheckout = false,
Amount = new decimal(0.01)
}, default));
Assert.IsType<ViewResult>(await publicApps.ViewCrowdfund(app.Id, string.Empty));
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty));
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>(); //Scenario 3: Enabled But Start Date > Now - Not Allowed
var publicApps = user.GetController<UIAppsPublicController>(); crowdfundViewModel.StartDate = DateTime.Today.AddDays(2);
crowdfundViewModel.Enabled = true;
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund() Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
{ Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
Amount = new decimal(0.01) {
}, default)); Amount = new decimal(0.01)
}, default));
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty)); //Scenario 4: Enabled But End Date < Now - Not Allowed
crowdfundViewModel.StartDate = DateTime.Today.AddDays(-2);
crowdfundViewModel.EndDate = DateTime.Today.AddDays(-1);
crowdfundViewModel.Enabled = true;
//Scenario 2: Not Enabled But Admin - Allowed Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<OkObjectResult>(await publicApps.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund() Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{ {
RedirectToCheckout = false, Amount = new decimal(0.01)
Amount = new decimal(0.01) }, default));
}, default));
Assert.IsType<ViewResult>(await publicApps.ViewCrowdfund(app.Id, string.Empty));
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty));
//Scenario 3: Enabled But Start Date > Now - Not Allowed //Scenario 5: Enabled and within correct timeframe, however target is enforced and Amount is Over - Not Allowed
crowdfundViewModel.StartDate = DateTime.Today.AddDays(2); crowdfundViewModel.StartDate = DateTime.Today.AddDays(-2);
crowdfundViewModel.Enabled = true; crowdfundViewModel.EndDate = DateTime.Today.AddDays(2);
crowdfundViewModel.Enabled = true;
crowdfundViewModel.TargetAmount = 1;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
Amount = new decimal(1.01)
}, default));
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); //Scenario 6: Allowed
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund() Assert.IsType<OkObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{ {
Amount = new decimal(0.01) Amount = new decimal(0.05)
}, default)); }, default));
//Scenario 4: Enabled But End Date < Now - Not Allowed
crowdfundViewModel.StartDate = DateTime.Today.AddDays(-2);
crowdfundViewModel.EndDate = DateTime.Today.AddDays(-1);
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
Amount = new decimal(0.01)
}, default));
//Scenario 5: Enabled and within correct timeframe, however target is enforced and Amount is Over - Not Allowed
crowdfundViewModel.StartDate = DateTime.Today.AddDays(-2);
crowdfundViewModel.EndDate = DateTime.Today.AddDays(2);
crowdfundViewModel.Enabled = true;
crowdfundViewModel.TargetAmount = 1;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
Amount = new decimal(1.01)
}, default));
//Scenario 6: Allowed
Assert.IsType<OkObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
Amount = new decimal(0.05)
}, default));
}
} }
[Fact(Timeout = LongRunningTestTimeout)] [Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanComputeCrowdfundModel() public async Task CanComputeCrowdfundModel()
{ {
using (var tester = CreateServerTester()) using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
var apps = user.GetController<UIAppsController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
vm.AppName = "test";
vm.SelectedAppType = AppType.Crowdfund.ToString();
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName });
TestLogs.LogInformation("We create an invoice with a hardcap");
var crowdfundViewModel = Assert.IsType<UpdateCrowdfundViewModel>(Assert
.IsType<ViewResult>(apps.UpdateCrowdfund(app.Id)).Model);
crowdfundViewModel.Enabled = true;
crowdfundViewModel.EndDate = null;
crowdfundViewModel.TargetAmount = 100;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.UseAllStoreInvoices = true;
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
var publicApps = user.GetController<UIAppsPublicController>();
var model = Assert.IsType<ViewCrowdfundViewModel>(Assert
.IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, String.Empty).Result).Model);
Assert.Equal(crowdfundViewModel.TargetAmount, model.TargetAmount);
Assert.Equal(crowdfundViewModel.EndDate, model.EndDate);
Assert.Equal(crowdfundViewModel.StartDate, model.StartDate);
Assert.Equal(crowdfundViewModel.TargetCurrency, model.TargetCurrency);
Assert.Equal(0m, model.Info.CurrentAmount);
Assert.Equal(0m, model.Info.CurrentPendingAmount);
Assert.Equal(0m, model.Info.ProgressPercentage);
TestLogs.LogInformation("Unpaid invoices should show as pending contribution because it is hardcap");
TestLogs.LogInformation("Because UseAllStoreInvoices is true, we can manually create an invoice and it should show as contribution");
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{ {
await tester.StartAsync(); Buyer = new Buyer() { email = "test@fwf.com" },
var user = tester.NewAccount(); Price = 1m,
await user.GrantAccessAsync(); Currency = "BTC",
user.RegisterDerivationScheme("BTC"); PosData = "posData",
await user.SetNetworkFeeMode(NetworkFeeMode.Never); ItemDesc = "Some description",
var apps = user.GetController<UIAppsController>(); TransactionSpeed = "high",
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model); FullNotifications = true
vm.AppName = "test"; }, Facade.Merchant);
vm.SelectedAppType = AppType.Crowdfund.ToString();
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName });
TestLogs.LogInformation("We create an invoice with a hardcap"); model = Assert.IsType<ViewCrowdfundViewModel>(Assert
var crowdfundViewModel = Assert.IsType<UpdateCrowdfundViewModel>(Assert .IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, string.Empty).Result).Model);
.IsType<ViewResult>(apps.UpdateCrowdfund(app.Id)).Model);
crowdfundViewModel.Enabled = true;
crowdfundViewModel.EndDate = null;
crowdfundViewModel.TargetAmount = 100;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.UseAllStoreInvoices = true;
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>(); Assert.Equal(0m, model.Info.CurrentAmount);
var publicApps = user.GetController<UIAppsPublicController>(); Assert.Equal(1m, model.Info.CurrentPendingAmount);
Assert.Equal(0m, model.Info.ProgressPercentage);
var model = Assert.IsType<ViewCrowdfundViewModel>(Assert Assert.Equal(1m, model.Info.PendingProgressPercentage);
.IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, String.Empty).Result).Model);
Assert.Equal(crowdfundViewModel.TargetAmount, model.TargetAmount);
Assert.Equal(crowdfundViewModel.EndDate, model.EndDate);
Assert.Equal(crowdfundViewModel.StartDate, model.StartDate);
Assert.Equal(crowdfundViewModel.TargetCurrency, model.TargetCurrency);
Assert.Equal(0m, model.Info.CurrentAmount);
Assert.Equal(0m, model.Info.CurrentPendingAmount);
Assert.Equal(0m, model.Info.ProgressPercentage);
TestLogs.LogInformation("Unpaid invoices should show as pending contribution because it is hardcap");
TestLogs.LogInformation("Because UseAllStoreInvoices is true, we can manually create an invoice and it should show as contribution");
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{
Buyer = new Buyer() { email = "test@fwf.com" },
Price = 1m,
Currency = "BTC",
PosData = "posData",
ItemDesc = "Some description",
TransactionSpeed = "high",
FullNotifications = true
}, Facade.Merchant);
TestLogs.LogInformation("Let's check current amount change once payment is confirmed");
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network);
tester.ExplorerNode.SendToAddress(invoiceAddress, invoice.BtcDue);
tester.ExplorerNode.Generate(1); // By default invoice confirmed at 1 block
TestUtils.Eventually(() =>
{
model = Assert.IsType<ViewCrowdfundViewModel>(Assert model = Assert.IsType<ViewCrowdfundViewModel>(Assert
.IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, string.Empty).Result).Model); .IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, String.Empty).Result).Model);
Assert.Equal(1m, model.Info.CurrentAmount);
Assert.Equal(0m, model.Info.CurrentAmount);
Assert.Equal(1m, model.Info.CurrentPendingAmount);
Assert.Equal(0m, model.Info.ProgressPercentage);
Assert.Equal(1m, model.Info.PendingProgressPercentage);
TestLogs.LogInformation("Let's check current amount change once payment is confirmed");
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network);
tester.ExplorerNode.SendToAddress(invoiceAddress, invoice.BtcDue);
tester.ExplorerNode.Generate(1); // By default invoice confirmed at 1 block
TestUtils.Eventually(() =>
{
model = Assert.IsType<ViewCrowdfundViewModel>(Assert
.IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, String.Empty).Result).Model);
Assert.Equal(1m, model.Info.CurrentAmount);
Assert.Equal(0m, model.Info.CurrentPendingAmount);
});
TestLogs.LogInformation("Because UseAllStoreInvoices is true, let's make sure the invoice is tagged");
var invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
Assert.True(invoiceEntity.Version >= InvoiceEntity.InternalTagSupport_Version);
Assert.Contains(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
crowdfundViewModel.UseAllStoreInvoices = false;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
TestLogs.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged");
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{
Buyer = new Buyer { email = "test@fwf.com" },
Price = 1m,
Currency = "BTC",
PosData = "posData",
ItemDesc = "Some description",
TransactionSpeed = "high",
FullNotifications = true
}, Facade.Merchant);
invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
Assert.DoesNotContain(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
TestLogs.LogInformation("After turning setting a softcap, let's check that only actual payments are counted");
crowdfundViewModel.EnforceTargetAmount = false;
crowdfundViewModel.UseAllStoreInvoices = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{
Buyer = new Buyer { email = "test@fwf.com" },
Price = 1m,
Currency = "BTC",
PosData = "posData",
ItemDesc = "Some description",
TransactionSpeed = "high",
FullNotifications = true
}, Facade.Merchant);
Assert.Equal(0m, model.Info.CurrentPendingAmount); Assert.Equal(0m, model.Info.CurrentPendingAmount);
invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network); });
await tester.ExplorerNode.SendToAddressAsync(invoiceAddress, Money.Coins(0.5m));
await tester.ExplorerNode.SendToAddressAsync(invoiceAddress, Money.Coins(0.2m)); TestLogs.LogInformation("Because UseAllStoreInvoices is true, let's make sure the invoice is tagged");
TestUtils.Eventually(() => var invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
{ Assert.True(invoiceEntity.Version >= InvoiceEntity.InternalTagSupport_Version);
model = Assert.IsType<ViewCrowdfundViewModel>(Assert Assert.Contains(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
.IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, string.Empty).Result).Model);
Assert.Equal(0.7m, model.Info.CurrentPendingAmount); crowdfundViewModel.UseAllStoreInvoices = false;
}); Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
}
TestLogs.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged");
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{
Buyer = new Buyer { email = "test@fwf.com" },
Price = 1m,
Currency = "BTC",
PosData = "posData",
ItemDesc = "Some description",
TransactionSpeed = "high",
FullNotifications = true
}, Facade.Merchant);
invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
Assert.DoesNotContain(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
TestLogs.LogInformation("After turning setting a softcap, let's check that only actual payments are counted");
crowdfundViewModel.EnforceTargetAmount = false;
crowdfundViewModel.UseAllStoreInvoices = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{
Buyer = new Buyer { email = "test@fwf.com" },
Price = 1m,
Currency = "BTC",
PosData = "posData",
ItemDesc = "Some description",
TransactionSpeed = "high",
FullNotifications = true
}, Facade.Merchant);
Assert.Equal(0m, model.Info.CurrentPendingAmount);
invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network);
await tester.ExplorerNode.SendToAddressAsync(invoiceAddress, Money.Coins(0.5m));
await tester.ExplorerNode.SendToAddressAsync(invoiceAddress, Money.Coins(0.2m));
TestUtils.Eventually(() =>
{
model = Assert.IsType<ViewCrowdfundViewModel>(Assert
.IsType<ViewResult>(publicApps.ViewCrowdfund(app.Id, string.Empty).Result).Model);
Assert.Equal(0.7m, model.Info.CurrentPendingAmount);
});
} }
} }
} }

View File

@ -42,22 +42,20 @@ namespace BTCPayServer.Tests
public async Task<JObject> GetNextRequest() public async Task<JObject> GetNextRequest()
{ {
using (CancellationTokenSource cancellation = new CancellationTokenSource(2000000)) using CancellationTokenSource cancellation = new CancellationTokenSource(2000000);
try
{ {
try JObject req = null;
while (!await _Requests.Reader.WaitToReadAsync(cancellation.Token) ||
!_Requests.Reader.TryRead(out req))
{ {
JObject req = null;
while (!await _Requests.Reader.WaitToReadAsync(cancellation.Token) ||
!_Requests.Reader.TryRead(out req))
{
}
return req;
}
catch (TaskCanceledException)
{
throw new Xunit.Sdk.XunitException("Callback to the webserver was expected, check if the callback url is accessible from internet");
} }
return req;
}
catch (TaskCanceledException)
{
throw new Xunit.Sdk.XunitException("Callback to the webserver was expected, check if the callback url is accessible from internet");
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -18,34 +18,32 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanAutoDetectLanguage() public async Task CanAutoDetectLanguage()
{ {
using (var tester = CreateServerTester()) using var tester = CreateServerTester();
{ await tester.StartAsync();
await tester.StartAsync(); var languageService = tester.PayTester.GetService<LanguageService>();
var languageService = tester.PayTester.GetService<LanguageService>();
// Most common format. First option does not have a quality score. Others do in descending order. // Most common format. First option does not have a quality score. Others do in descending order.
// Result should be nl-NL (because the default weight is 1 for nl) // Result should be nl-NL (because the default weight is 1 for nl)
var lang1 = languageService.FindLanguageInAcceptLanguageHeader("nl,fr;q=0.7,en;q=0.5"); var lang1 = languageService.FindLanguageInAcceptLanguageHeader("nl,fr;q=0.7,en;q=0.5");
Assert.NotNull(lang1); Assert.NotNull(lang1);
Assert.Equal("nl-NL", lang1?.Code); Assert.Equal("nl-NL", lang1?.Code);
// Most common format. First option does not have a quality score. Others do in descending order. This time the first option includes a country. // Most common format. First option does not have a quality score. Others do in descending order. This time the first option includes a country.
// Result should be nl-NL (because the default weight is 1 for nl-BE and it does not exist in BTCPay Server, but nl-NL does and applies too for language "nl") // Result should be nl-NL (because the default weight is 1 for nl-BE and it does not exist in BTCPay Server, but nl-NL does and applies too for language "nl")
var lang2 = languageService.FindLanguageInAcceptLanguageHeader("nl-BE,fr;q=0.7,en;q=0.5"); var lang2 = languageService.FindLanguageInAcceptLanguageHeader("nl-BE,fr;q=0.7,en;q=0.5");
Assert.NotNull(lang2); Assert.NotNull(lang2);
Assert.Equal("nl-NL", lang2?.Code); Assert.Equal("nl-NL", lang2?.Code);
// Unusual format, but still valid. All values have a quality score and not ordered. // Unusual format, but still valid. All values have a quality score and not ordered.
// Result should be fr-FR (because 0.7 is the highest quality score) // Result should be fr-FR (because 0.7 is the highest quality score)
var lang3 = languageService.FindLanguageInAcceptLanguageHeader("nl;q=0.1,fr;q=0.7,en;q=0.5"); var lang3 = languageService.FindLanguageInAcceptLanguageHeader("nl;q=0.1,fr;q=0.7,en;q=0.5");
Assert.NotNull(lang3); Assert.NotNull(lang3);
Assert.Equal("fr-FR", lang3?.Code); Assert.Equal("fr-FR", lang3?.Code);
// Unusual format, but still valid. Some language is given that we don't have and a wildcard for everything else. // Unusual format, but still valid. Some language is given that we don't have and a wildcard for everything else.
// Result should be NULL, because "xx" does not exist and * is a wildcard and has no meaning. // Result should be NULL, because "xx" does not exist and * is a wildcard and has no meaning.
var lang4 = languageService.FindLanguageInAcceptLanguageHeader("xx,*;q=0.5"); var lang4 = languageService.FindLanguageInAcceptLanguageHeader("xx,*;q=0.5");
Assert.Null(lang4); Assert.Null(lang4);
}
} }
} }
} }

View File

@ -22,23 +22,22 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanUsePoSApp1() public async Task CanUsePoSApp1()
{ {
using (var tester = CreateServerTester()) using var tester = CreateServerTester();
{ await tester.StartAsync();
await tester.StartAsync(); var user = tester.NewAccount();
var user = tester.NewAccount(); await user.GrantAccessAsync();
await user.GrantAccessAsync(); user.RegisterDerivationScheme("BTC");
user.RegisterDerivationScheme("BTC"); var apps = user.GetController<UIAppsController>();
var apps = user.GetController<UIAppsController>(); var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model); vm.AppName = "test";
vm.AppName = "test"; vm.SelectedAppType = AppType.PointOfSale.ToString();
vm.SelectedAppType = AppType.PointOfSale.ToString(); Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result); var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model); var app = appList.Apps[0];
var app = appList.Apps[0]; apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName });
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName }); var vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert
var vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert .IsType<ViewResult>(apps.UpdatePointOfSale(app.Id)).Model);
.IsType<ViewResult>(apps.UpdatePointOfSale(app.Id)).Model); vmpos.Template = @"
vmpos.Template = @"
apple: apple:
price: 5.0 price: 5.0
title: good apple title: good apple
@ -49,25 +48,24 @@ donation:
price: 1.02 price: 1.02
custom: true custom: true
"; ";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result); Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert
.IsType<ViewResult>(apps.UpdatePointOfSale(app.Id)).Model); .IsType<ViewResult>(apps.UpdatePointOfSale(app.Id)).Model);
var publicApps = user.GetController<UIAppsPublicController>(); var publicApps = user.GetController<UIAppsPublicController>();
var vmview = var vmview =
Assert.IsType<ViewPointOfSaleViewModel>(Assert Assert.IsType<ViewPointOfSaleViewModel>(Assert
.IsType<ViewResult>(publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).Result).Model); .IsType<ViewResult>(publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).Result).Model);
// apple shouldn't be available since we it's set to "disabled: true" above // apple shouldn't be available since we it's set to "disabled: true" above
Assert.Equal(2, vmview.Items.Length); Assert.Equal(2, vmview.Items.Length);
Assert.Equal("orange", vmview.Items[0].Title); Assert.Equal("orange", vmview.Items[0].Title);
Assert.Equal("donation", vmview.Items[1].Title); Assert.Equal("donation", vmview.Items[1].Title);
// orange is available // orange is available
Assert.IsType<RedirectToActionResult>(publicApps Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "orange").Result); .ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "orange").Result);
// apple is not found // apple is not found
Assert.IsType<NotFoundResult>(publicApps Assert.IsType<NotFoundResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "apple").Result); .ViewPointOfSale(app.Id, PosViewType.Cart, 0, null, null, null, null, "apple").Result);
}
} }
} }
} }

View File

@ -23,36 +23,35 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanPlayWithPSBT() public async Task CanPlayWithPSBT()
{ {
using (var tester = CreateServerTester()) using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
var invoice = user.BitPay.CreateInvoice(new Invoice()
{ {
await tester.StartAsync(); Price = 10,
var user = tester.NewAccount(); Currency = "USD",
user.GrantAccess(); PosData = "posData",
user.RegisterDerivationScheme("BTC"); OrderId = "orderId",
var invoice = user.BitPay.CreateInvoice(new Invoice() ItemDesc = "Some \", description",
{ FullNotifications = true
Price = 10, }, Facade.Merchant);
Currency = "USD", var cashCow = tester.ExplorerNode;
PosData = "posData", var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
OrderId = "orderId", cashCow.SendToAddress(invoiceAddress, Money.Coins(1.5m));
ItemDesc = "Some \", description", TestUtils.Eventually(() =>
FullNotifications = true {
}, Facade.Merchant); invoice = user.BitPay.GetInvoice(invoice.Id);
var cashCow = tester.ExplorerNode; Assert.Equal("paid", invoice.Status);
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); });
cashCow.SendToAddress(invoiceAddress, Money.Coins(1.5m));
TestUtils.Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal("paid", invoice.Status);
});
var walletController = user.GetController<UIWalletsController>(); var walletController = user.GetController<UIWalletsController>();
var walletId = new WalletId(user.StoreId, "BTC"); var walletId = new WalletId(user.StoreId, "BTC");
var sendDestination = new Key().PubKey.Hash.GetAddress(user.SupportedNetwork.NBitcoinNetwork).ToString(); var sendDestination = new Key().PubKey.Hash.GetAddress(user.SupportedNetwork.NBitcoinNetwork).ToString();
var sendModel = new WalletSendModel() var sendModel = new WalletSendModel()
{ {
Outputs = new List<WalletSendModel.TransactionOutput>() Outputs = new List<WalletSendModel.TransactionOutput>()
{ {
new WalletSendModel.TransactionOutput() new WalletSendModel.TransactionOutput()
{ {
@ -60,81 +59,80 @@ namespace BTCPayServer.Tests
Amount = 0.1m, Amount = 0.1m,
} }
}, },
FeeSatoshiPerByte = 1, FeeSatoshiPerByte = 1,
CurrentBalance = 1.5m CurrentBalance = 1.5m
}; };
string redirectedPSBT = AssertRedirectedPSBT(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt"), nameof(walletController.WalletPSBT)); string redirectedPSBT = AssertRedirectedPSBT(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt"), nameof(walletController.WalletPSBT));
var vmPSBT = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel() { PSBT = redirectedPSBT }).AssertViewModelAsync<WalletPSBTViewModel>(); var vmPSBT = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel() { PSBT = redirectedPSBT }).AssertViewModelAsync<WalletPSBTViewModel>();
var unsignedPSBT = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork); var unsignedPSBT = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork);
Assert.NotNull(vmPSBT.Decoded); Assert.NotNull(vmPSBT.Decoded);
var filePSBT = (FileContentResult)(await walletController.WalletPSBT(walletId, vmPSBT, "save-psbt")); var filePSBT = (FileContentResult)(await walletController.WalletPSBT(walletId, vmPSBT, "save-psbt"));
PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork); PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork);
var vmPSBT2 = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel var vmPSBT2 = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel
{
SigningContext = new SigningContextModel
{ {
SigningContext = new SigningContextModel PSBT = AssertRedirectedPSBT(await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
{ }
PSBT = AssertRedirectedPSBT(await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady)) }).AssertViewModelAsync<WalletPSBTViewModel>();
} Assert.NotEmpty(vmPSBT2.Inputs.Where(i => i.Error != null));
}).AssertViewModelAsync<WalletPSBTViewModel>(); Assert.Equal(vmPSBT.PSBT, vmPSBT2.SigningContext.PSBT);
Assert.NotEmpty(vmPSBT2.Inputs.Where(i => i.Error != null));
Assert.Equal(vmPSBT.PSBT, vmPSBT2.SigningContext.PSBT);
var signedPSBT = unsignedPSBT.Clone(); var signedPSBT = unsignedPSBT.Clone();
signedPSBT.SignAll(user.DerivationScheme, user.GenerateWalletResponseV.AccountHDKey, user.GenerateWalletResponseV.AccountKeyPath); signedPSBT.SignAll(user.DerivationScheme, user.GenerateWalletResponseV.AccountHDKey, user.GenerateWalletResponseV.AccountKeyPath);
vmPSBT.PSBT = signedPSBT.ToBase64(); vmPSBT.PSBT = signedPSBT.ToBase64();
var psbtReady = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel var psbtReady = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel
{
SigningContext = new SigningContextModel
{ {
SigningContext = new SigningContextModel PSBT = AssertRedirectedPSBT(await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
{ }
PSBT = AssertRedirectedPSBT(await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady)) }).AssertViewModelAsync<WalletPSBTViewModel>();
} Assert.Equal(2 + 1, psbtReady.Destinations.Count); // The fee is a destination
}).AssertViewModelAsync<WalletPSBTViewModel>(); Assert.Contains(psbtReady.Destinations, d => d.Destination == sendDestination && !d.Positive);
Assert.Equal(2 + 1, psbtReady.Destinations.Count); // The fee is a destination Assert.Contains(psbtReady.Destinations, d => d.Positive);
Assert.Contains(psbtReady.Destinations, d => d.Destination == sendDestination && !d.Positive);
Assert.Contains(psbtReady.Destinations, d => d.Positive);
vmPSBT.PSBT = unsignedPSBT.ToBase64(); vmPSBT.PSBT = unsignedPSBT.ToBase64();
var combineVM = await walletController.WalletPSBT(walletId, vmPSBT, "combine").AssertViewModelAsync<WalletPSBTCombineViewModel>(); var combineVM = await walletController.WalletPSBT(walletId, vmPSBT, "combine").AssertViewModelAsync<WalletPSBTCombineViewModel>();
Assert.Equal(vmPSBT.PSBT, combineVM.OtherPSBT); Assert.Equal(vmPSBT.PSBT, combineVM.OtherPSBT);
combineVM.PSBT = signedPSBT.ToBase64(); combineVM.PSBT = signedPSBT.ToBase64();
var psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM), nameof(walletController.WalletPSBT)); var psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM), nameof(walletController.WalletPSBT));
var signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork); var signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
Assert.True(signedPSBT.TryFinalize(out _)); Assert.True(signedPSBT.TryFinalize(out _));
Assert.True(signedPSBT2.TryFinalize(out _)); Assert.True(signedPSBT2.TryFinalize(out _));
Assert.Equal(signedPSBT, signedPSBT2); Assert.Equal(signedPSBT, signedPSBT2);
// Can use uploaded file? // Can use uploaded file?
combineVM.PSBT = null; combineVM.PSBT = null;
combineVM.UploadedPSBTFile = TestUtils.GetFormFile("signedPSBT", signedPSBT.ToBytes()); combineVM.UploadedPSBTFile = TestUtils.GetFormFile("signedPSBT", signedPSBT.ToBytes());
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM), nameof(walletController.WalletPSBT)); psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM), nameof(walletController.WalletPSBT));
signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork); signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
Assert.True(signedPSBT.TryFinalize(out _)); Assert.True(signedPSBT.TryFinalize(out _));
Assert.True(signedPSBT2.TryFinalize(out _)); Assert.True(signedPSBT2.TryFinalize(out _));
Assert.Equal(signedPSBT, signedPSBT2); Assert.Equal(signedPSBT, signedPSBT2);
var ready = (await walletController.WalletPSBT(walletId, new WalletPSBTViewModel var ready = (await walletController.WalletPSBT(walletId, new WalletPSBTViewModel
{ {
SigningContext = new SigningContextModel(signedPSBT) SigningContext = new SigningContextModel(signedPSBT)
})).AssertViewModel<WalletPSBTViewModel>(); })).AssertViewModel<WalletPSBTViewModel>();
Assert.Equal(signedPSBT.ToBase64(), ready.SigningContext.PSBT); Assert.Equal(signedPSBT.ToBase64(), ready.SigningContext.PSBT);
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"), nameof(walletController.WalletPSBT)); psbt = AssertRedirectedPSBT(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"), nameof(walletController.WalletPSBT));
Assert.Equal(signedPSBT.ToBase64(), psbt); Assert.Equal(signedPSBT.ToBase64(), psbt);
var redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast")); var redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast"));
Assert.Equal(nameof(walletController.WalletTransactions), redirect.ActionName); Assert.Equal(nameof(walletController.WalletTransactions), redirect.ActionName);
//test base64 psbt file //test base64 psbt file
Assert.False(string.IsNullOrEmpty(Assert.IsType<WalletPSBTViewModel>( Assert.False(string.IsNullOrEmpty(Assert.IsType<WalletPSBTViewModel>(
Assert.IsType<ViewResult>( Assert.IsType<ViewResult>(
await walletController.WalletPSBT(walletId, await walletController.WalletPSBT(walletId,
new WalletPSBTViewModel new WalletPSBTViewModel
{ {
UploadedPSBTFile = TestUtils.GetFormFile("base64", signedPSBT.ToBase64()) UploadedPSBTFile = TestUtils.GetFormFile("base64", signedPSBT.ToBase64())
})).Model).PSBT)); })).Model).PSBT));
}
} }
private static string AssertRedirectedPSBT(IActionResult view, string actionName) private static string AssertRedirectedPSBT(IActionResult view, string actionName)

File diff suppressed because it is too large Load Diff

View File

@ -24,222 +24,216 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanCreateViewUpdateAndDeletePaymentRequest() public async Task CanCreateViewUpdateAndDeletePaymentRequest()
{ {
using (var tester = CreateServerTester()) using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
var user2 = tester.NewAccount();
await user2.GrantAccessAsync();
var paymentRequestController = user.GetController<UIPaymentRequestController>();
var guestpaymentRequestController = user2.GetController<UIPaymentRequestController>();
var request = new UpdatePaymentRequestViewModel
{ {
await tester.StartAsync(); Title = "original juice",
var user = tester.NewAccount(); Currency = "BTC",
await user.GrantAccessAsync(); Amount = 1,
user.RegisterDerivationScheme("BTC"); StoreId = user.StoreId,
Description = "description"
};
var id = Assert
.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(null, request))
.RouteValues.Values.Last().ToString();
var user2 = tester.NewAccount(); paymentRequestController.HttpContext.SetPaymentRequestData(new PaymentRequestData { Id = id, StoreDataId = request.StoreId });
await user2.GrantAccessAsync(); // Permission guard for guests editing
Assert
.IsType<NotFoundResult>(guestpaymentRequestController.EditPaymentRequest(user.StoreId, id));
var paymentRequestController = user.GetController<UIPaymentRequestController>(); request.Title = "update";
var guestpaymentRequestController = user2.GetController<UIPaymentRequestController>(); Assert.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(id, request));
var request = new UpdatePaymentRequestViewModel
{
Title = "original juice",
Currency = "BTC",
Amount = 1,
StoreId = user.StoreId,
Description = "description"
};
var id = Assert
.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(null, request))
.RouteValues.Values.Last().ToString();
paymentRequestController.HttpContext.SetPaymentRequestData(new PaymentRequestData { Id = id, StoreDataId = request.StoreId });
// Permission guard for guests editing
Assert
.IsType<NotFoundResult>(guestpaymentRequestController.EditPaymentRequest(user.StoreId, id));
request.Title = "update";
Assert.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(id, request));
Assert.Equal(request.Title,
Assert.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Title);
Assert.False(string.IsNullOrEmpty(id));
Assert.Equal(request.Title,
Assert.IsType<ViewPaymentRequestViewModel>(Assert Assert.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model); .IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Title);
// Archive Assert.False(string.IsNullOrEmpty(id));
Assert
.IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id));
Assert.True(Assert
.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived);
Assert.Empty(Assert Assert.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ListPaymentRequestsViewModel>(Assert .IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model);
.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests(user.StoreId)).Model).Items);
// Unarchive // Archive
Assert Assert
.IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id)); .IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id));
Assert.True(Assert
.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived);
Assert.False(Assert Assert.Empty(Assert
.IsType<ViewPaymentRequestViewModel>(Assert .IsType<ListPaymentRequestsViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived); .IsType<ViewResult>(await paymentRequestController.GetPaymentRequests(user.StoreId)).Model).Items);
Assert.Single(Assert // Unarchive
.IsType<ListPaymentRequestsViewModel>(Assert Assert
.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests(user.StoreId)).Model).Items); .IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id));
}
Assert.False(Assert
.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived);
Assert.Single(Assert
.IsType<ListPaymentRequestsViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests(user.StoreId)).Model).Items);
} }
[Fact(Timeout = 60 * 2 * 1000)] [Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanPayPaymentRequestWhenPossible() public async Task CanPayPaymentRequestWhenPossible()
{ {
using (var tester = CreateServerTester()) using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
var paymentRequestController = user.GetController<UIPaymentRequestController>();
Assert.IsType<NotFoundResult>(
await paymentRequestController.PayPaymentRequest(Guid.NewGuid().ToString()));
var request = new UpdatePaymentRequestViewModel()
{ {
await tester.StartAsync(); Title = "original juice",
var user = tester.NewAccount(); Currency = "BTC",
await user.GrantAccessAsync(); Amount = 1,
user.RegisterDerivationScheme("BTC"); StoreId = user.StoreId,
Description = "description"
};
var response = Assert
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
.RouteValues.Last();
var paymentRequestController = user.GetController<UIPaymentRequestController>(); var invoiceId = Assert
.IsType<OkObjectResult>(
await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false)).Value
.ToString();
Assert.IsType<NotFoundResult>( var actionResult = Assert
await paymentRequestController.PayPaymentRequest(Guid.NewGuid().ToString())); .IsType<RedirectToActionResult>(
await paymentRequestController.PayPaymentRequest(response.Value.ToString()));
Assert.Equal("Checkout", actionResult.ActionName);
Assert.Equal("UIInvoice", actionResult.ControllerName);
Assert.Contains(actionResult.RouteValues,
pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
var request = new UpdatePaymentRequestViewModel() var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
{ Assert.Equal(1, invoice.Price);
Title = "original juice",
Currency = "BTC",
Amount = 1,
StoreId = user.StoreId,
Description = "description"
};
var response = Assert
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
.RouteValues.Last();
var invoiceId = Assert request = new UpdatePaymentRequestViewModel()
.IsType<OkObjectResult>( {
await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false)).Value Title = "original juice with expiry",
.ToString(); Currency = "BTC",
Amount = 1,
ExpiryDate = DateTime.Today.Subtract(TimeSpan.FromDays(2)),
StoreId = user.StoreId,
Description = "description"
};
var actionResult = Assert response = Assert
.IsType<RedirectToActionResult>( .IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
await paymentRequestController.PayPaymentRequest(response.Value.ToString())); .RouteValues.Last();
Assert.Equal("Checkout", actionResult.ActionName); Assert
Assert.Equal("UIInvoice", actionResult.ControllerName); .IsType<BadRequestObjectResult>(
Assert.Contains(actionResult.RouteValues, await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false));
pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
Assert.Equal(1, invoice.Price);
request = new UpdatePaymentRequestViewModel()
{
Title = "original juice with expiry",
Currency = "BTC",
Amount = 1,
ExpiryDate = DateTime.Today.Subtract(TimeSpan.FromDays(2)),
StoreId = user.StoreId,
Description = "description"
};
response = Assert
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
.RouteValues.Last();
Assert
.IsType<BadRequestObjectResult>(
await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false));
}
} }
[Fact(Timeout = 60 * 2 * 1000)] [Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanCancelPaymentWhenPossible() public async Task CanCancelPaymentWhenPossible()
{ {
using (var tester = CreateServerTester()) using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
var paymentRequestController = user.GetController<UIPaymentRequestController>();
Assert.IsType<NotFoundResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(Guid.NewGuid().ToString(), false));
var request = new UpdatePaymentRequestViewModel()
{ {
await tester.StartAsync(); Title = "original juice",
var user = tester.NewAccount(); Currency = "BTC",
user.GrantAccess(); Amount = 1,
user.RegisterDerivationScheme("BTC"); StoreId = user.StoreId,
Description = "description"
};
var response = Assert
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
.RouteValues.Last();
var invoiceId = response.Value.ToString();
await paymentRequestController.PayPaymentRequest(invoiceId, false);
Assert.IsType<BadRequestObjectResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(invoiceId, false));
var paymentRequestController = user.GetController<UIPaymentRequestController>(); request.AllowCustomPaymentAmounts = true;
Assert.IsType<NotFoundResult>(await response = Assert
paymentRequestController.CancelUnpaidPendingInvoice(Guid.NewGuid().ToString(), false)); .IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
.RouteValues.Last();
var request = new UpdatePaymentRequestViewModel() var paymentRequestId = response.Value.ToString();
{
Title = "original juice",
Currency = "BTC",
Amount = 1,
StoreId = user.StoreId,
Description = "description"
};
var response = Assert
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
.RouteValues.Last();
var invoiceId = response.Value.ToString();
await paymentRequestController.PayPaymentRequest(invoiceId, false);
Assert.IsType<BadRequestObjectResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(invoiceId, false));
request.AllowCustomPaymentAmounts = true; invoiceId = Assert
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false))
.Value
.ToString();
response = Assert var actionResult = Assert
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result) .IsType<RedirectToActionResult>(
.RouteValues.Last(); await paymentRequestController.PayPaymentRequest(response.Value.ToString()));
var paymentRequestId = response.Value.ToString(); Assert.Equal("Checkout", actionResult.ActionName);
Assert.Equal("UIInvoice", actionResult.ControllerName);
Assert.Contains(actionResult.RouteValues,
pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
invoiceId = Assert var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false)) Assert.Equal(InvoiceState.ToString(InvoiceStatusLegacy.New), invoice.Status);
.Value Assert.IsType<OkObjectResult>(await
.ToString(); paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false));
var actionResult = Assert invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
.IsType<RedirectToActionResult>( Assert.Equal(InvoiceState.ToString(InvoiceStatusLegacy.Invalid), invoice.Status);
await paymentRequestController.PayPaymentRequest(response.Value.ToString()));
Assert.Equal("Checkout", actionResult.ActionName); Assert.IsType<BadRequestObjectResult>(await
Assert.Equal("UIInvoice", actionResult.ControllerName); paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false));
Assert.Contains(actionResult.RouteValues,
pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant); invoiceId = Assert
Assert.Equal(InvoiceState.ToString(InvoiceStatusLegacy.New), invoice.Status); .IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false))
Assert.IsType<OkObjectResult>(await .Value
paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false)); .ToString();
invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant); await user.BitPay.GetInvoiceAsync(invoiceId, Facade.Merchant);
Assert.Equal(InvoiceState.ToString(InvoiceStatusLegacy.Invalid), invoice.Status);
Assert.IsType<BadRequestObjectResult>(await //a hack to generate invoices for the payment request is to manually create an invoice with an order id that matches:
paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false)); user.BitPay.CreateInvoice(new Invoice(1, "USD")
{
invoiceId = Assert OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(paymentRequestId)
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false)) });
.Value //shouldn't crash
.ToString(); await paymentRequestController.ViewPaymentRequest(paymentRequestId);
await paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId);
await user.BitPay.GetInvoiceAsync(invoiceId, Facade.Merchant);
//a hack to generate invoices for the payment request is to manually create an invoice with an order id that matches:
user.BitPay.CreateInvoice(new Invoice(1, "USD")
{
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(paymentRequestId)
});
//shouldn't crash
await paymentRequestController.ViewPaymentRequest(paymentRequestId);
await paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId);
}
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -37,40 +37,38 @@ namespace BTCPayServer.Tests
[FactWithSecret("AzureBlobStorageConnectionString")] [FactWithSecret("AzureBlobStorageConnectionString")]
public async Task CanUseAzureBlobStorage() public async Task CanUseAzureBlobStorage()
{ {
using (var tester = CreateServerTester()) using var tester = CreateServerTester();
{ await tester.StartAsync();
await tester.StartAsync(); var user = tester.NewAccount();
var user = tester.NewAccount(); user.GrantAccess();
user.GrantAccess(); var controller = tester.PayTester.GetController<UIServerController>(user.UserId, user.StoreId);
var controller = tester.PayTester.GetController<UIServerController>(user.UserId, user.StoreId); var azureBlobStorageConfiguration = Assert.IsType<AzureBlobStorageConfiguration>(Assert
var azureBlobStorageConfiguration = Assert.IsType<AzureBlobStorageConfiguration>(Assert .IsType<ViewResult>(await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString()))
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString())) .Model);
.Model);
azureBlobStorageConfiguration.ConnectionString = FactWithSecretAttribute.GetFromSecrets("AzureBlobStorageConnectionString"); azureBlobStorageConfiguration.ConnectionString = FactWithSecretAttribute.GetFromSecrets("AzureBlobStorageConnectionString");
azureBlobStorageConfiguration.ContainerName = "testscontainer"; azureBlobStorageConfiguration.ContainerName = "testscontainer";
Assert.IsType<ViewResult>( Assert.IsType<ViewResult>(
await controller.EditAzureBlobStorageStorageProvider(azureBlobStorageConfiguration)); await controller.EditAzureBlobStorageStorageProvider(azureBlobStorageConfiguration));
var shouldBeRedirectingToAzureStorageConfigPage = var shouldBeRedirectingToAzureStorageConfigPage =
Assert.IsType<RedirectToActionResult>(await controller.Storage()); Assert.IsType<RedirectToActionResult>(await controller.Storage());
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToAzureStorageConfigPage.ActionName); Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToAzureStorageConfigPage.ActionName);
Assert.Equal(StorageProvider.AzureBlobStorage, Assert.Equal(StorageProvider.AzureBlobStorage,
shouldBeRedirectingToAzureStorageConfigPage.RouteValues["provider"]); shouldBeRedirectingToAzureStorageConfigPage.RouteValues["provider"]);
//seems like azure config worked, let's see if the conn string was actually saved //seems like azure config worked, let's see if the conn string was actually saved
Assert.Equal(azureBlobStorageConfiguration.ConnectionString, Assert Assert.Equal(azureBlobStorageConfiguration.ConnectionString, Assert
.IsType<AzureBlobStorageConfiguration>(Assert .IsType<AzureBlobStorageConfiguration>(Assert
.IsType<ViewResult>( .IsType<ViewResult>(
await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString())) await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString()))
.Model).ConnectionString); .Model).ConnectionString);
await UnitTest1.CanUploadRemoveFiles(controller); await UnitTest1.CanUploadRemoveFiles(controller);
}
} }
[Fact] [Fact]
@ -338,24 +336,22 @@ namespace BTCPayServer.Tests
[Fact] [Fact]
public async Task CanUseExchangeSpecificRate() public async Task CanUseExchangeSpecificRate()
{ {
using (var tester = CreateServerTester()) using var tester = CreateServerTester();
{ tester.PayTester.MockRates = false;
tester.PayTester.MockRates = false; await tester.StartAsync();
await tester.StartAsync(); var user = tester.NewAccount();
var user = tester.NewAccount(); await user.GrantAccessAsync();
await user.GrantAccessAsync(); user.RegisterDerivationScheme("BTC");
user.RegisterDerivationScheme("BTC"); List<decimal> rates = new List<decimal>();
List<decimal> rates = new List<decimal>(); rates.Add(await CreateInvoice(tester, user, "coingecko"));
rates.Add(await CreateInvoice(tester, user, "coingecko")); var bitflyer = await CreateInvoice(tester, user, "bitflyer", "JPY");
var bitflyer = await CreateInvoice(tester, user, "bitflyer", "JPY"); var bitflyer2 = await CreateInvoice(tester, user, "bitflyer", "JPY");
var bitflyer2 = await CreateInvoice(tester, user, "bitflyer", "JPY"); Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache
Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache rates.Add(bitflyer);
rates.Add(bitflyer);
foreach (var rate in rates) foreach (var rate in rates)
{ {
Assert.Single(rates.Where(r => r == rate)); Assert.Single(rates.Where(r => r == rate));
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -16,23 +16,21 @@ namespace BTCPayServer.Tests
{ {
lock (_portLock) lock (_portLock)
{ {
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
while (true)
{ {
while (true) try
{ {
try var port = _nextPort++;
socket.Bind(new IPEndPoint(IPAddress.Loopback, port));
return port;
}
catch (SocketException)
{
// Retry unless exhausted
if (_nextPort == 65536)
{ {
var port = _nextPort++; throw;
socket.Bind(new IPEndPoint(IPAddress.Loopback, port));
return port;
}
catch (SocketException)
{
// Retry unless exhausted
if (_nextPort == 65536)
{
throw;
}
} }
} }
} }

View File

@ -871,11 +871,9 @@ namespace BTCPayServer.Controllers
{ {
try try
{ {
using (var sshClient = await _Options.SSHSettings.ConnectAsync()) using var sshClient = await _Options.SSHSettings.ConnectAsync();
{ var result = await sshClient.RunBash("cat ~/.ssh/authorized_keys", TimeSpan.FromSeconds(10));
var result = await sshClient.RunBash("cat ~/.ssh/authorized_keys", TimeSpan.FromSeconds(10)); vm.SSHKeyFileContent = result.Output;
vm.SSHKeyFileContent = result.Output;
}
} }
catch { } catch { }
} }
@ -1106,17 +1104,13 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
try try
{ {
using (var fileStream = new FileStream( using var fileStream = new FileStream(
fi.FullName, fi.FullName,
FileMode.Open, FileMode.Open,
FileAccess.Read, FileAccess.Read,
FileShare.ReadWrite)) FileShare.ReadWrite);
{ using var reader = new StreamReader(fileStream);
using (var reader = new StreamReader(fileStream)) vm.Log = await reader.ReadToEndAsync();
{
vm.Log = await reader.ReadToEndAsync();
}
}
} }
catch catch
{ {

View File

@ -810,10 +810,8 @@ namespace BTCPayServer.Controllers
private async Task<string> ReadAllText(IFormFile file) private async Task<string> ReadAllText(IFormFile file)
{ {
using (var stream = new StreamReader(file.OpenReadStream())) using var stream = new StreamReader(file.OpenReadStream());
{ return await stream.ReadToEndAsync();
return await stream.ReadToEndAsync();
}
} }
private string WalletWarning(bool isHotWallet, string info) private string WalletWarning(bool isHotWallet, string info)

View File

@ -125,11 +125,9 @@ namespace BTCPayServer
{ {
if (webSocket.State == WebSocketState.Open) if (webSocket.State == WebSocketState.Open)
{ {
using (CancellationTokenSource cts = new CancellationTokenSource()) using CancellationTokenSource cts = new CancellationTokenSource();
{ cts.CancelAfter(5000);
cts.CancelAfter(5000); await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cts.Token);
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cts.Token);
}
} }
} }
catch { } catch { }

View File

@ -40,12 +40,10 @@ retry:
Logs.Configuration.LogInformation($"SSH settings detected, testing connection to {_options.SSHSettings.Username}@{_options.SSHSettings.Server} on port {_options.SSHSettings.Port} ..."); Logs.Configuration.LogInformation($"SSH settings detected, testing connection to {_options.SSHSettings.Username}@{_options.SSHSettings.Server} on port {_options.SSHSettings.Port} ...");
try try
{ {
using (var connection = await _options.SSHSettings.ConnectAsync(_cancellationTokenSource.Token)) using var connection = await _options.SSHSettings.ConnectAsync(_cancellationTokenSource.Token);
{ await connection.DisconnectAsync(_cancellationTokenSource.Token);
await connection.DisconnectAsync(_cancellationTokenSource.Token); Logs.Configuration.LogInformation($"SSH connection succeeded");
Logs.Configuration.LogInformation($"SSH connection succeeded"); canUseSSH = true;
canUseSSH = true;
}
} }
catch (Renci.SshNet.Common.SshAuthenticationException ex) catch (Renci.SshNet.Common.SshAuthenticationException ex)
{ {

View File

@ -59,13 +59,11 @@ namespace BTCPayServer.HostedServices
} }
} }
} }
using (var delayCancel = CancellationTokenSource.CreateLinkedTokenSource(Cancellation)) using var delayCancel = CancellationTokenSource.CreateLinkedTokenSource(Cancellation);
{ var delay = Task.Delay(Period, delayCancel.Token);
var delay = Task.Delay(Period, delayCancel.Token); var changed = SettingsRepository.WaitSettingsChanged<DynamicDnsSettings>(Cancellation);
var changed = SettingsRepository.WaitSettingsChanged<DynamicDnsSettings>(Cancellation); await Task.WhenAny(delay, changed);
await Task.WhenAny(delay, changed); delayCancel.Cancel();
delayCancel.Cancel();
}
} }
} }
} }

View File

@ -463,164 +463,154 @@ retry:
private async Task ConvertConvertWalletKeyPathRoots() private async Task ConvertConvertWalletKeyPathRoots()
{ {
bool save = false; bool save = false;
using (var ctx = _DBContextFactory.CreateContext()) using var ctx = _DBContextFactory.CreateContext();
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
{ {
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
{
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
var blob = store.GetStoreBlob(); var blob = store.GetStoreBlob();
if (blob.AdditionalData.TryGetValue("walletKeyPathRoots", out var walletKeyPathRootsJToken)) if (blob.AdditionalData.TryGetValue("walletKeyPathRoots", out var walletKeyPathRootsJToken))
{
var walletKeyPathRoots = walletKeyPathRootsJToken.ToObject<Dictionary<string, string>>();
if (!(walletKeyPathRoots?.Any() is true))
continue;
foreach (var scheme in store.GetSupportedPaymentMethods(_NetworkProvider)
.OfType<DerivationSchemeSettings>())
{ {
var walletKeyPathRoots = walletKeyPathRootsJToken.ToObject<Dictionary<string, string>>(); if (walletKeyPathRoots.TryGetValue(scheme.PaymentId.ToString().ToLowerInvariant(),
out var root))
if (!(walletKeyPathRoots?.Any() is true))
continue;
foreach (var scheme in store.GetSupportedPaymentMethods(_NetworkProvider)
.OfType<DerivationSchemeSettings>())
{ {
if (walletKeyPathRoots.TryGetValue(scheme.PaymentId.ToString().ToLowerInvariant(), scheme.AccountKeyPath = new NBitcoin.KeyPath(root);
out var root)) store.SetSupportedPaymentMethod(scheme);
{ save = true;
scheme.AccountKeyPath = new NBitcoin.KeyPath(root);
store.SetSupportedPaymentMethod(scheme);
save = true;
}
} }
blob.AdditionalData.Remove("walletKeyPathRoots");
store.SetStoreBlob(blob);
} }
#pragma warning restore CS0618 // Type or member is obsolete
blob.AdditionalData.Remove("walletKeyPathRoots");
store.SetStoreBlob(blob);
} }
if (save) #pragma warning restore CS0618 // Type or member is obsolete
await ctx.SaveChangesAsync();
} }
if (save)
await ctx.SaveChangesAsync();
} }
private async Task ConvertCrowdfundOldSettings() private async Task ConvertCrowdfundOldSettings()
{ {
using (var ctx = _DBContextFactory.CreateContext()) using var ctx = _DBContextFactory.CreateContext();
foreach (var app in await ctx.Apps.Where(a => a.AppType == "Crowdfund").ToArrayAsync())
{ {
foreach (var app in await ctx.Apps.Where(a => a.AppType == "Crowdfund").ToArrayAsync()) var settings = app.GetSettings<Services.Apps.CrowdfundSettings>();
{
var settings = app.GetSettings<Services.Apps.CrowdfundSettings>();
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
if (settings.UseAllStoreInvoices) if (settings.UseAllStoreInvoices)
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
{ {
app.TagAllInvoices = true; app.TagAllInvoices = true;
}
} }
await ctx.SaveChangesAsync();
} }
await ctx.SaveChangesAsync();
} }
private async Task MigratePaymentMethodCriteria() private async Task MigratePaymentMethodCriteria()
{ {
using (var ctx = _DBContextFactory.CreateContext()) using var ctx = _DBContextFactory.CreateContext();
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
{ {
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync()) var blob = store.GetStoreBlob();
CurrencyValue onChainMinValue = null;
CurrencyValue lightningMaxValue = null;
if (blob.AdditionalData.TryGetValue("onChainMinValue", out var onChainMinValueJToken))
{ {
var blob = store.GetStoreBlob(); CurrencyValue.TryParse(onChainMinValueJToken.Value<string>(), out onChainMinValue);
blob.AdditionalData.Remove("onChainMinValue");
CurrencyValue onChainMinValue = null;
CurrencyValue lightningMaxValue = null;
if (blob.AdditionalData.TryGetValue("onChainMinValue", out var onChainMinValueJToken))
{
CurrencyValue.TryParse(onChainMinValueJToken.Value<string>(), out onChainMinValue);
blob.AdditionalData.Remove("onChainMinValue");
}
if (blob.AdditionalData.TryGetValue("lightningMaxValue", out var lightningMaxValueJToken))
{
CurrencyValue.TryParse(lightningMaxValueJToken.Value<string>(), out lightningMaxValue);
blob.AdditionalData.Remove("lightningMaxValue");
}
blob.PaymentMethodCriteria = store.GetEnabledPaymentIds(_NetworkProvider).Select(paymentMethodId =>
{
var matchedFromBlob =
blob.PaymentMethodCriteria?.SingleOrDefault(criteria => criteria.PaymentMethod == paymentMethodId && criteria.Value != null);
return matchedFromBlob switch
{
null when paymentMethodId.PaymentType == LightningPaymentType.Instance &&
lightningMaxValue != null => new PaymentMethodCriteria()
{
Above = false,
PaymentMethod = paymentMethodId,
Value = lightningMaxValue
},
null when paymentMethodId.PaymentType == BitcoinPaymentType.Instance &&
onChainMinValue != null => new PaymentMethodCriteria()
{
Above = true,
PaymentMethod = paymentMethodId,
Value = onChainMinValue
},
_ => new PaymentMethodCriteria()
{
PaymentMethod = paymentMethodId,
Above = matchedFromBlob?.Above ?? true,
Value = matchedFromBlob?.Value
}
};
}).ToList();
store.SetStoreBlob(blob);
} }
if (blob.AdditionalData.TryGetValue("lightningMaxValue", out var lightningMaxValueJToken))
{
CurrencyValue.TryParse(lightningMaxValueJToken.Value<string>(), out lightningMaxValue);
blob.AdditionalData.Remove("lightningMaxValue");
}
blob.PaymentMethodCriteria = store.GetEnabledPaymentIds(_NetworkProvider).Select(paymentMethodId =>
{
var matchedFromBlob =
blob.PaymentMethodCriteria?.SingleOrDefault(criteria => criteria.PaymentMethod == paymentMethodId && criteria.Value != null);
return matchedFromBlob switch
{
null when paymentMethodId.PaymentType == LightningPaymentType.Instance &&
lightningMaxValue != null => new PaymentMethodCriteria()
{
Above = false,
PaymentMethod = paymentMethodId,
Value = lightningMaxValue
},
null when paymentMethodId.PaymentType == BitcoinPaymentType.Instance &&
onChainMinValue != null => new PaymentMethodCriteria()
{
Above = true,
PaymentMethod = paymentMethodId,
Value = onChainMinValue
},
_ => new PaymentMethodCriteria()
{
PaymentMethod = paymentMethodId,
Above = matchedFromBlob?.Above ?? true,
Value = matchedFromBlob?.Value
}
};
}).ToList();
await ctx.SaveChangesAsync(); store.SetStoreBlob(blob);
} }
await ctx.SaveChangesAsync();
} }
private async Task ConvertNetworkFeeProperty() private async Task ConvertNetworkFeeProperty()
{ {
using (var ctx = _DBContextFactory.CreateContext()) using var ctx = _DBContextFactory.CreateContext();
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
{ {
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync()) var blob = store.GetStoreBlob();
if (blob.AdditionalData.TryGetValue("networkFeeDisabled", out var networkFeeModeJToken))
{ {
var blob = store.GetStoreBlob(); var networkFeeMode = networkFeeModeJToken.ToObject<bool?>();
if (blob.AdditionalData.TryGetValue("networkFeeDisabled", out var networkFeeModeJToken)) if (networkFeeMode != null)
{ {
var networkFeeMode = networkFeeModeJToken.ToObject<bool?>(); blob.NetworkFeeMode = networkFeeMode.Value ? NetworkFeeMode.Never : NetworkFeeMode.Always;
if (networkFeeMode != null)
{
blob.NetworkFeeMode = networkFeeMode.Value ? NetworkFeeMode.Never : NetworkFeeMode.Always;
}
blob.AdditionalData.Remove("networkFeeDisabled");
store.SetStoreBlob(blob);
} }
blob.AdditionalData.Remove("networkFeeDisabled");
store.SetStoreBlob(blob);
} }
await ctx.SaveChangesAsync();
} }
await ctx.SaveChangesAsync();
} }
private async Task ConvertMultiplierToSpread() private async Task ConvertMultiplierToSpread()
{ {
using (var ctx = _DBContextFactory.CreateContext()) using var ctx = _DBContextFactory.CreateContext();
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
{ {
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync()) var blob = store.GetStoreBlob();
decimal multiplier = 1.0m;
if (blob.AdditionalData.TryGetValue("rateRules", out var rateRulesJToken))
{ {
var blob = store.GetStoreBlob(); var rateRules = new Serializer(null).ToObject<List<RateRule_Obsolete>>(rateRulesJToken.ToString());
decimal multiplier = 1.0m; if (rateRules != null && rateRules.Count != 0)
if (blob.AdditionalData.TryGetValue("rateRules", out var rateRulesJToken))
{ {
var rateRules = new Serializer(null).ToObject<List<RateRule_Obsolete>>(rateRulesJToken.ToString()); foreach (var rule in rateRules)
if (rateRules != null && rateRules.Count != 0)
{ {
foreach (var rule in rateRules) multiplier = rule.Apply(null, multiplier);
{
multiplier = rule.Apply(null, multiplier);
}
} }
blob.AdditionalData.Remove("rateRules");
blob.Spread = Math.Min(1.0m, Math.Max(0m, -(multiplier - 1.0m)));
store.SetStoreBlob(blob);
} }
blob.AdditionalData.Remove("rateRules");
blob.Spread = Math.Min(1.0m, Math.Max(0m, -(multiplier - 1.0m)));
store.SetStoreBlob(blob);
} }
await ctx.SaveChangesAsync();
} }
await ctx.SaveChangesAsync();
} }
public class RateRule_Obsolete public class RateRule_Obsolete
@ -646,22 +636,20 @@ retry:
private async Task DeprecatedLightningConnectionStringCheck() private async Task DeprecatedLightningConnectionStringCheck()
{ {
using (var ctx = _DBContextFactory.CreateContext()) using var ctx = _DBContextFactory.CreateContext();
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync())
{ {
foreach (var store in await ctx.Stores.AsQueryable().ToArrayAsync()) foreach (var method in store.GetSupportedPaymentMethods(_NetworkProvider).OfType<Payments.Lightning.LightningSupportedPaymentMethod>())
{ {
foreach (var method in store.GetSupportedPaymentMethods(_NetworkProvider).OfType<Payments.Lightning.LightningSupportedPaymentMethod>()) var lightning = method.GetExternalLightningUrl();
if (lightning?.IsLegacy is true)
{ {
var lightning = method.GetExternalLightningUrl(); method.SetLightningUrl(lightning);
if (lightning?.IsLegacy is true) store.SetSupportedPaymentMethod(method);
{
method.SetLightningUrl(lightning);
store.SetSupportedPaymentMethod(method);
}
} }
} }
await ctx.SaveChangesAsync();
} }
await ctx.SaveChangesAsync();
} }
} }
} }

View File

@ -20,17 +20,15 @@ namespace BTCPayServer.Hosting
{ {
_BundlesByName = new Lazy<Dictionary<string, Bundle>>(() => _BundlesByName = new Lazy<Dictionary<string, Bundle>>(() =>
{ {
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("BTCPayServer.bundleconfig.json")) using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("BTCPayServer.bundleconfig.json");
using (var reader = new StreamReader(stream, Encoding.UTF8)) using var reader = new StreamReader(stream, Encoding.UTF8);
{ var content = reader.ReadToEnd();
var content = reader.ReadToEnd(); return JArray.Parse(content).OfType<JObject>()
return JArray.Parse(content).OfType<JObject>() .Select(jobj => new Bundle()
.Select(jobj => new Bundle() {
{ Name = jobj.Property("name", StringComparison.OrdinalIgnoreCase)?.Value.Value<string>() ?? jobj.Property("outputFileName", StringComparison.OrdinalIgnoreCase).Value.Value<string>(),
Name = jobj.Property("name", StringComparison.OrdinalIgnoreCase)?.Value.Value<string>() ?? jobj.Property("outputFileName", StringComparison.OrdinalIgnoreCase).Value.Value<string>(), OutputFileUrl = Path.Combine(hosting.ContentRootPath, jobj.Property("outputFileName", StringComparison.OrdinalIgnoreCase).Value.Value<string>())
OutputFileUrl = Path.Combine(hosting.ContentRootPath, jobj.Property("outputFileName", StringComparison.OrdinalIgnoreCase).Value.Value<string>()) }).ToDictionary(o => o.Name, o => o);
}).ToDictionary(o => o.Name, o => o);
}
}, true); }, true);
} }
else else

View File

@ -43,10 +43,8 @@ namespace BTCPayServer.Models
} }
context.HttpContext.Response.Headers.Add("Content-Type", new Microsoft.Extensions.Primitives.StringValues("application/json")); context.HttpContext.Response.Headers.Add("Content-Type", new Microsoft.Extensions.Primitives.StringValues("application/json"));
var str = JsonConvert.SerializeObject(jobj); var str = JsonConvert.SerializeObject(jobj);
await using (var writer = new StreamWriter(context.HttpContext.Response.Body, new UTF8Encoding(false), 1024 * 10, true)) await using var writer = new StreamWriter(context.HttpContext.Response.Body, new UTF8Encoding(false), 1024 * 10, true);
{ await writer.WriteAsync(str);
await writer.WriteAsync(str);
}
} }
} }
} }

View File

@ -130,42 +130,40 @@ namespace BTCPayServer.Payments.Lightning
try try
{ {
using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT)) using var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT);
var client = CreateLightningClient(supportedPaymentMethod, network);
LightningNodeInformation info;
try
{ {
var client = CreateLightningClient(supportedPaymentMethod, network); info = await client.GetInfo(cts.Token);
LightningNodeInformation info;
try
{
info = await client.GetInfo(cts.Token);
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
throw new PaymentMethodUnavailableException("The lightning node did not reply in a timely manner");
}
catch (Exception ex)
{
throw new PaymentMethodUnavailableException($"Error while connecting to the API: {ex.Message}" +
(!string.IsNullOrEmpty(ex.InnerException?.Message) ? $" ({ex.InnerException.Message})" : ""));
}
var nodeInfo = preferOnion != null && info.NodeInfoList.Any(i => i.IsTor == preferOnion)
? info.NodeInfoList.Where(i => i.IsTor == preferOnion.Value).ToArray()
: info.NodeInfoList.Select(i => i).ToArray();
// Maybe the user does not have an easily accessible ln node. Node info should be optional. The UI also supports this.
// if (!nodeInfo.Any())
// {
// throw new PaymentMethodUnavailableException("No lightning node public address has been configured");
// }
var blocksGap = summary.Status.ChainHeight - info.BlockHeight;
if (blocksGap > 10)
{
throw new PaymentMethodUnavailableException($"The lightning node is not synched ({blocksGap} blocks left)");
}
return nodeInfo;
} }
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
throw new PaymentMethodUnavailableException("The lightning node did not reply in a timely manner");
}
catch (Exception ex)
{
throw new PaymentMethodUnavailableException($"Error while connecting to the API: {ex.Message}" +
(!string.IsNullOrEmpty(ex.InnerException?.Message) ? $" ({ex.InnerException.Message})" : ""));
}
var nodeInfo = preferOnion != null && info.NodeInfoList.Any(i => i.IsTor == preferOnion)
? info.NodeInfoList.Where(i => i.IsTor == preferOnion.Value).ToArray()
: info.NodeInfoList.Select(i => i).ToArray();
// Maybe the user does not have an easily accessible ln node. Node info should be optional. The UI also supports this.
// if (!nodeInfo.Any())
// {
// throw new PaymentMethodUnavailableException("No lightning node public address has been configured");
// }
var blocksGap = summary.Status.ChainHeight - info.BlockHeight;
if (blocksGap > 10)
{
throw new PaymentMethodUnavailableException($"The lightning node is not synched ({blocksGap} blocks left)");
}
return nodeInfo;
} }
catch (Exception e) when (!throws) catch (Exception e) when (!throws)
{ {
@ -197,9 +195,7 @@ namespace BTCPayServer.Payments.Lightning
if (!Utils.TryParseEndpoint(nodeInfo.Host, nodeInfo.Port, out var endpoint)) if (!Utils.TryParseEndpoint(nodeInfo.Host, nodeInfo.Port, out var endpoint))
throw new PaymentMethodUnavailableException($"Could not parse the endpoint {nodeInfo.Host}"); throw new PaymentMethodUnavailableException($"Could not parse the endpoint {nodeInfo.Host}");
using (var tcp = await _socketFactory.ConnectAsync(endpoint, cancellation)) using var tcp = await _socketFactory.ConnectAsync(endpoint, cancellation);
{
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -458,38 +458,36 @@ namespace BTCPayServer.Payments.Lightning
try try
{ {
var lightningClient = _lightningClientFactory.Create(ConnectionString, _network); var lightningClient = _lightningClientFactory.Create(ConnectionString, _network);
using (var session = await lightningClient.Listen(cancellation)) using var session = await lightningClient.Listen(cancellation);
// Just in case the payment arrived after our last poll but before we listened.
await PollAllListenedInvoices(cancellation);
if (_ErrorAlreadyLogged)
{ {
// Just in case the payment arrived after our last poll but before we listened. Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Could reconnect successfully to {ConnectionString.BaseUri}");
await PollAllListenedInvoices(cancellation); }
if (_ErrorAlreadyLogged) _ErrorAlreadyLogged = false;
while (!_ListenedInvoices.IsEmpty)
{
var notification = await session.WaitInvoice(cancellation);
if (!_ListenedInvoices.TryGetValue(notification.Id, out var listenedInvoice))
continue;
if (notification.Id == listenedInvoice.PaymentMethodDetails.InvoiceId &&
(notification.BOLT11 == listenedInvoice.PaymentMethodDetails.BOLT11 ||
BOLT11PaymentRequest.Parse(notification.BOLT11, _network.NBitcoinNetwork).PaymentHash ==
listenedInvoice.PaymentMethodDetails.GetPaymentHash(_network.NBitcoinNetwork)))
{ {
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Could reconnect successfully to {ConnectionString.BaseUri}"); if (notification.Status == LightningInvoiceStatus.Paid &&
} notification.PaidAt.HasValue && notification.Amount != null)
_ErrorAlreadyLogged = false;
while (!_ListenedInvoices.IsEmpty)
{
var notification = await session.WaitInvoice(cancellation);
if (!_ListenedInvoices.TryGetValue(notification.Id, out var listenedInvoice))
continue;
if (notification.Id == listenedInvoice.PaymentMethodDetails.InvoiceId &&
(notification.BOLT11 == listenedInvoice.PaymentMethodDetails.BOLT11 ||
BOLT11PaymentRequest.Parse(notification.BOLT11, _network.NBitcoinNetwork).PaymentHash ==
listenedInvoice.PaymentMethodDetails.GetPaymentHash(_network.NBitcoinNetwork)))
{ {
if (notification.Status == LightningInvoiceStatus.Paid && if (await AddPayment(notification, listenedInvoice.InvoiceId, listenedInvoice.PaymentMethod.GetId().PaymentType))
notification.PaidAt.HasValue && notification.Amount != null)
{ {
if (await AddPayment(notification, listenedInvoice.InvoiceId, listenedInvoice.PaymentMethod.GetId().PaymentType)) Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Payment detected via notification ({listenedInvoice.InvoiceId})");
{
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Payment detected via notification ({listenedInvoice.InvoiceId})");
}
_ListenedInvoices.TryRemove(notification.Id, out var _);
}
else if (notification.Status == LightningInvoiceStatus.Expired)
{
_ListenedInvoices.TryRemove(notification.Id, out var _);
} }
_ListenedInvoices.TryRemove(notification.Id, out var _);
}
else if (notification.Status == LightningInvoiceStatus.Expired)
{
_ListenedInvoices.TryRemove(notification.Id, out var _);
} }
} }
} }

View File

@ -29,21 +29,17 @@ namespace BTCPayServer.Security.Bitpay
{ {
if (sin == null) if (sin == null)
return Array.Empty<BitTokenEntity>(); return Array.Empty<BitTokenEntity>();
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
{ return (await ctx.PairedSINData.Where(p => p.SIN == sin)
return (await ctx.PairedSINData.Where(p => p.SIN == sin) .ToArrayAsync())
.ToArrayAsync()) .Select(p => CreateTokenEntity(p))
.Select(p => CreateTokenEntity(p)) .ToArray();
.ToArray();
}
} }
public async Task<String> GetStoreIdFromAPIKey(string apiKey) public async Task<String> GetStoreIdFromAPIKey(string apiKey)
{ {
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
{ return await ctx.ApiKeys.Where(o => o.Id == apiKey).Select(o => o.StoreId).FirstOrDefaultAsync();
return await ctx.ApiKeys.Where(o => o.Id == apiKey).Select(o => o.StoreId).FirstOrDefaultAsync();
}
} }
public async Task GenerateLegacyAPIKey(string storeId) public async Task GenerateLegacyAPIKey(string storeId)
@ -57,16 +53,14 @@ namespace BTCPayServer.Security.Bitpay
generated[i] = chars[(int)(RandomUtils.GetUInt32() % generated.Length)]; generated[i] = chars[(int)(RandomUtils.GetUInt32() % generated.Length)];
} }
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
var existing = await ctx.ApiKeys.Where(o => o.StoreId == storeId && o.Type == APIKeyType.Legacy).ToListAsync();
if (existing.Any())
{ {
var existing = await ctx.ApiKeys.Where(o => o.StoreId == storeId && o.Type == APIKeyType.Legacy).ToListAsync(); ctx.ApiKeys.RemoveRange(existing);
if (existing.Any())
{
ctx.ApiKeys.RemoveRange(existing);
}
ctx.ApiKeys.Add(new APIKeyData() { Id = new string(generated), StoreId = storeId });
await ctx.SaveChangesAsync().ConfigureAwait(false);
} }
ctx.ApiKeys.Add(new APIKeyData() { Id = new string(generated), StoreId = storeId });
await ctx.SaveChangesAsync().ConfigureAwait(false);
} }
public async Task RevokeLegacyAPIKeys(string storeId) public async Task RevokeLegacyAPIKeys(string storeId)
@ -77,19 +71,15 @@ namespace BTCPayServer.Security.Bitpay
return; return;
} }
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
{ ctx.ApiKeys.RemoveRange(keys.Select(s => new APIKeyData() { Id = s }));
ctx.ApiKeys.RemoveRange(keys.Select(s => new APIKeyData() { Id = s })); await ctx.SaveChangesAsync();
await ctx.SaveChangesAsync();
}
} }
public async Task<string[]> GetLegacyAPIKeys(string storeId) public async Task<string[]> GetLegacyAPIKeys(string storeId)
{ {
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
{ return await ctx.ApiKeys.Where(o => o.StoreId == storeId && o.Type == APIKeyType.Legacy).Select(c => c.Id).ToArrayAsync();
return await ctx.ApiKeys.Where(o => o.StoreId == storeId && o.Type == APIKeyType.Legacy).Select(c => c.Id).ToArrayAsync();
}
} }
private BitTokenEntity CreateTokenEntity(PairedSINData data) private BitTokenEntity CreateTokenEntity(PairedSINData data)
@ -131,41 +121,35 @@ namespace BTCPayServer.Security.Bitpay
public async Task<PairingCodeEntity> UpdatePairingCode(PairingCodeEntity pairingCodeEntity) public async Task<PairingCodeEntity> UpdatePairingCode(PairingCodeEntity pairingCodeEntity)
{ {
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
{ var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeEntity.Id);
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeEntity.Id); pairingCode.Label = pairingCodeEntity.Label;
pairingCode.Label = pairingCodeEntity.Label; await ctx.SaveChangesAsync();
await ctx.SaveChangesAsync(); return CreatePairingCodeEntity(pairingCode);
return CreatePairingCodeEntity(pairingCode);
}
} }
public async Task<PairingResult> PairWithStoreAsync(string pairingCodeId, string storeId) public async Task<PairingResult> PairWithStoreAsync(string pairingCodeId, string storeId)
{ {
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
{ var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId); if (pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
if (pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow) return PairingResult.Expired;
return PairingResult.Expired; pairingCode.StoreDataId = storeId;
pairingCode.StoreDataId = storeId; var result = await ActivateIfComplete(ctx, pairingCode);
var result = await ActivateIfComplete(ctx, pairingCode); await ctx.SaveChangesAsync();
await ctx.SaveChangesAsync(); return result;
return result;
}
} }
public async Task<PairingResult> PairWithSINAsync(string pairingCodeId, string sin) public async Task<PairingResult> PairWithSINAsync(string pairingCodeId, string sin)
{ {
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
{ var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId);
var pairingCode = await ctx.PairingCodes.FindAsync(pairingCodeId); if (pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow)
if (pairingCode == null || pairingCode.Expiration < DateTimeOffset.UtcNow) return PairingResult.Expired;
return PairingResult.Expired; pairingCode.SIN = sin;
pairingCode.SIN = sin; var result = await ActivateIfComplete(ctx, pairingCode);
var result = await ActivateIfComplete(ctx, pairingCode); await ctx.SaveChangesAsync();
await ctx.SaveChangesAsync(); return result;
return result;
}
} }
@ -195,20 +179,16 @@ namespace BTCPayServer.Security.Bitpay
public async Task<BitTokenEntity[]> GetTokensByStoreIdAsync(string storeId) public async Task<BitTokenEntity[]> GetTokensByStoreIdAsync(string storeId)
{ {
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
{ return (await ctx.PairedSINData.Where(p => p.StoreDataId == storeId).ToListAsync())
return (await ctx.PairedSINData.Where(p => p.StoreDataId == storeId).ToListAsync()) .Select(c => CreateTokenEntity(c))
.Select(c => CreateTokenEntity(c)) .ToArray();
.ToArray();
}
} }
public async Task<PairingCodeEntity> GetPairingAsync(string pairingCode) public async Task<PairingCodeEntity> GetPairingAsync(string pairingCode)
{ {
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
{ return CreatePairingCodeEntity(await ctx.PairingCodes.FindAsync(pairingCode));
return CreatePairingCodeEntity(await ctx.PairingCodes.FindAsync(pairingCode));
}
} }
private PairingCodeEntity CreatePairingCodeEntity(PairingCodeData data) private PairingCodeEntity CreatePairingCodeEntity(PairingCodeData data)
@ -229,26 +209,22 @@ namespace BTCPayServer.Security.Bitpay
public async Task<bool> DeleteToken(string tokenId) public async Task<bool> DeleteToken(string tokenId)
{ {
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
{ var token = await ctx.PairedSINData.FindAsync(tokenId);
var token = await ctx.PairedSINData.FindAsync(tokenId); if (token == null)
if (token == null) return false;
return false; ctx.PairedSINData.Remove(token);
ctx.PairedSINData.Remove(token); await ctx.SaveChangesAsync();
await ctx.SaveChangesAsync(); return true;
return true;
}
} }
public async Task<BitTokenEntity> GetToken(string tokenId) public async Task<BitTokenEntity> GetToken(string tokenId)
{ {
using (var ctx = _Factory.CreateContext()) using var ctx = _Factory.CreateContext();
{ var token = await ctx.PairedSINData.FindAsync(tokenId);
var token = await ctx.PairedSINData.FindAsync(tokenId); if (token == null)
if (token == null) return null;
return null; return CreateTokenEntity(token);
return CreateTokenEntity(token);
}
} }
} }

View File

@ -28,19 +28,17 @@ namespace BTCPayServer.Security.Greenfield
public async Task<List<APIKeyData>> GetKeys(APIKeyQuery query) public async Task<List<APIKeyData>> GetKeys(APIKeyQuery query)
{ {
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
var queryable = context.ApiKeys.AsQueryable();
if (query != null)
{ {
var queryable = context.ApiKeys.AsQueryable(); if (query.UserId != null && query.UserId.Any())
if (query != null)
{ {
if (query.UserId != null && query.UserId.Any()) queryable = queryable.Where(data => query.UserId.Contains(data.UserId));
{
queryable = queryable.Where(data => query.UserId.Contains(data.UserId));
}
} }
return await queryable.ToListAsync();
} }
return await queryable.ToListAsync();
} }
public async Task CreateKey(APIKeyData key) public async Task CreateKey(APIKeyData key)
@ -50,11 +48,9 @@ namespace BTCPayServer.Security.Greenfield
throw new InvalidOperationException("cannot save a bitpay legacy api key with this repository"); throw new InvalidOperationException("cannot save a bitpay legacy api key with this repository");
} }
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
{ await context.ApiKeys.AddAsync(key);
await context.ApiKeys.AddAsync(key); await context.SaveChangesAsync();
await context.SaveChangesAsync();
}
} }
public async Task<bool> Remove(string id, string getUserId) public async Task<bool> Remove(string id, string getUserId)

View File

@ -209,53 +209,47 @@ namespace BTCPayServer.Services.Apps
public async Task<StoreData[]> GetOwnedStores(string userId) public async Task<StoreData[]> GetOwnedStores(string userId)
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ return await ctx.UserStore
return await ctx.UserStore .Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner)
.Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner) .Select(u => u.StoreData)
.Select(u => u.StoreData) .ToArrayAsync();
.ToArrayAsync();
}
} }
public async Task<bool> DeleteApp(AppData appData) public async Task<bool> DeleteApp(AppData appData)
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ ctx.Apps.Add(appData);
ctx.Apps.Add(appData); ctx.Entry(appData).State = EntityState.Deleted;
ctx.Entry(appData).State = EntityState.Deleted; return await ctx.SaveChangesAsync() == 1;
return await ctx.SaveChangesAsync() == 1;
}
} }
public async Task<ListAppsViewModel.ListAppViewModel[]> GetAllApps(string userId, bool allowNoUser = false, string storeId = null) public async Task<ListAppsViewModel.ListAppViewModel[]> GetAllApps(string userId, bool allowNoUser = false, string storeId = null)
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
var listApps = await ctx.UserStore
.Where(us =>
(allowNoUser && string.IsNullOrEmpty(userId) || us.ApplicationUserId == userId) &&
(storeId == null || us.StoreDataId == storeId))
.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();
foreach (ListAppsViewModel.ListAppViewModel app in listApps)
{ {
var listApps = await ctx.UserStore app.ViewStyle = await GetAppViewStyleAsync(app.Id, app.AppType);
.Where(us =>
(allowNoUser && string.IsNullOrEmpty(userId) || us.ApplicationUserId == userId) &&
(storeId == null || us.StoreDataId == storeId))
.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();
foreach (ListAppsViewModel.ListAppViewModel app in listApps)
{
app.ViewStyle = await GetAppViewStyleAsync(app.Id, app.AppType);
}
return listApps;
} }
return listApps;
} }
public async Task<string> GetAppViewStyleAsync(string appId, string appType) public async Task<string> GetAppViewStyleAsync(string appId, string appType)
@ -284,33 +278,29 @@ namespace BTCPayServer.Services.Apps
public async Task<List<AppData>> GetApps(string[] appIds, bool includeStore = false) public async Task<List<AppData>> GetApps(string[] appIds, bool includeStore = false)
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ var query = ctx.Apps
var query = ctx.Apps .Where(us => appIds.Contains(us.Id));
.Where(us => appIds.Contains(us.Id));
if (includeStore) if (includeStore)
{ {
query = query.Include(data => data.StoreData); query = query.Include(data => data.StoreData);
}
return await query.ToListAsync();
} }
return await query.ToListAsync();
} }
public async Task<AppData> GetApp(string appId, AppType? appType, bool includeStore = false) public async Task<AppData> GetApp(string appId, AppType? appType, bool includeStore = false)
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ var query = ctx.Apps
var query = ctx.Apps .Where(us => us.Id == appId &&
.Where(us => us.Id == appId && (appType == null || us.AppType == appType.ToString()));
(appType == null || us.AppType == appType.ToString()));
if (includeStore) if (includeStore)
{ {
query = query.Include(data => data.StoreData); query = query.Include(data => data.StoreData);
}
return await query.FirstOrDefaultAsync();
} }
return await query.FirstOrDefaultAsync();
} }
public Task<StoreData> GetStore(AppData app) public Task<StoreData> GetStore(AppData app)
@ -525,39 +515,35 @@ namespace BTCPayServer.Services.Apps
{ {
if (userId == null || appId == null) if (userId == null || appId == null)
return null; return null;
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ var app = await ctx.UserStore
var app = await ctx.UserStore .Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner)
.Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner) .SelectMany(us => us.StoreData.Apps.Where(a => a.Id == appId))
.SelectMany(us => us.StoreData.Apps.Where(a => a.Id == appId)) .FirstOrDefaultAsync();
.FirstOrDefaultAsync(); if (app == null)
if (app == null) return null;
return null; if (type != null && type.Value.ToString() != app.AppType)
if (type != null && type.Value.ToString() != app.AppType) return null;
return null; return app;
return app;
}
} }
public async Task UpdateOrCreateApp(AppData app) public async Task UpdateOrCreateApp(AppData app)
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
if (string.IsNullOrEmpty(app.Id))
{ {
if (string.IsNullOrEmpty(app.Id)) app.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20));
{ app.Created = DateTimeOffset.UtcNow;
app.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20)); await ctx.Apps.AddAsync(app);
app.Created = DateTimeOffset.UtcNow;
await ctx.Apps.AddAsync(app);
}
else
{
ctx.Apps.Update(app);
ctx.Entry(app).Property(data => data.Created).IsModified = false;
ctx.Entry(app).Property(data => data.Id).IsModified = false;
ctx.Entry(app).Property(data => data.AppType).IsModified = false;
}
await ctx.SaveChangesAsync();
} }
else
{
ctx.Apps.Update(app);
ctx.Entry(app).Property(data => data.Created).IsModified = false;
ctx.Entry(app).Property(data => data.Id).IsModified = false;
ctx.Entry(app).Property(data => data.AppType).IsModified = false;
}
await ctx.SaveChangesAsync();
} }
private static bool TryParseJson(string json, out JObject result) private static bool TryParseJson(string json, out JObject result)

View File

@ -28,14 +28,12 @@ namespace BTCPayServer.Services
public async Task UpdateInvoiceExpiry(string invoiceId, DateTimeOffset dateTimeOffset) public async Task UpdateInvoiceExpiry(string invoiceId, DateTimeOffset dateTimeOffset)
{ {
using (var ctx = _applicationDbContextFactory.CreateContext()) using var ctx = _applicationDbContextFactory.CreateContext();
{ var invoiceData = await ctx.Invoices.FindAsync(invoiceId).ConfigureAwait(false);
var invoiceData = await ctx.Invoices.FindAsync(invoiceId).ConfigureAwait(false); if (invoiceData == null)
if (invoiceData == null) return;
return; // TODO change the expiry time. But how?
// TODO change the expiry time. But how? await ctx.SaveChangesAsync().ConfigureAwait(false);
await ctx.SaveChangesAsync().ConfigureAwait(false);
}
} }
Task IHostedService.StartAsync(CancellationToken cancellationToken) Task IHostedService.StartAsync(CancellationToken cancellationToken)

View File

@ -44,21 +44,19 @@ namespace BTCPayServer.Services
{ {
ArgumentNullException.ThrowIfNull(transaction); ArgumentNullException.ThrowIfNull(transaction);
ArgumentNullException.ThrowIfNull(network); ArgumentNullException.ThrowIfNull(network);
using (var db = _dbContextFactory.CreateContext()) using var db = _dbContextFactory.CreateContext();
db.PlannedTransactions.Add(new PlannedTransaction()
{
Id = $"{network.CryptoCode}-{transaction.GetHash()}",
BroadcastAt = broadcastTime,
Blob = transaction.ToBytes()
});
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException)
{ {
db.PlannedTransactions.Add(new PlannedTransaction()
{
Id = $"{network.CryptoCode}-{transaction.GetHash()}",
BroadcastAt = broadcastTime,
Blob = transaction.ToBytes()
});
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException)
{
}
} }
} }

View File

@ -67,38 +67,32 @@ namespace BTCPayServer.Services.Invoices
public async Task<bool> RemovePendingInvoice(string invoiceId) public async Task<bool> RemovePendingInvoice(string invoiceId)
{ {
Logs.PayServer.LogInformation($"Remove pending invoice {invoiceId}"); Logs.PayServer.LogInformation($"Remove pending invoice {invoiceId}");
using (var ctx = _applicationDbContextFactory.CreateContext()) using var ctx = _applicationDbContextFactory.CreateContext();
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId });
try
{ {
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId }); await ctx.SaveChangesAsync();
try return true;
{
await ctx.SaveChangesAsync();
return true;
}
catch (DbUpdateException) { return false; }
} }
catch (DbUpdateException) { return false; }
} }
public async Task<IEnumerable<InvoiceEntity>> GetInvoicesFromAddresses(string[] addresses) public async Task<IEnumerable<InvoiceEntity>> GetInvoicesFromAddresses(string[] addresses)
{ {
using (var db = _applicationDbContextFactory.CreateContext()) using var db = _applicationDbContextFactory.CreateContext();
{ return (await db.AddressInvoices
return (await db.AddressInvoices .Include(a => a.InvoiceData.Payments)
.Include(a => a.InvoiceData.Payments)
#pragma warning disable CS0618 #pragma warning disable CS0618
.Where(a => addresses.Contains(a.Address)) .Where(a => addresses.Contains(a.Address))
#pragma warning restore CS0618 #pragma warning restore CS0618
.Select(a => a.InvoiceData) .Select(a => a.InvoiceData)
.ToListAsync()).Select(ToEntity); .ToListAsync()).Select(ToEntity);
}
} }
public async Task<string[]> GetPendingInvoices() public async Task<string[]> GetPendingInvoices()
{ {
using (var ctx = _applicationDbContextFactory.CreateContext()) using var ctx = _applicationDbContextFactory.CreateContext();
{ return await ctx.PendingInvoices.AsQueryable().Select(data => data.Id).ToArrayAsync();
return await ctx.PendingInvoices.AsQueryable().Select(data => data.Id).ToArrayAsync();
}
} }
public async Task<List<Data.WebhookDeliveryData>> GetWebhookDeliveries(string invoiceId) public async Task<List<Data.WebhookDeliveryData>> GetWebhookDeliveries(string invoiceId)
@ -115,40 +109,34 @@ namespace BTCPayServer.Services.Invoices
public async Task<AppData[]> GetAppsTaggingStore(string storeId) public async Task<AppData[]> GetAppsTaggingStore(string storeId)
{ {
ArgumentNullException.ThrowIfNull(storeId); ArgumentNullException.ThrowIfNull(storeId);
using (var ctx = _applicationDbContextFactory.CreateContext()) using var ctx = _applicationDbContextFactory.CreateContext();
{ return await ctx.Apps.Where(a => a.StoreDataId == storeId && a.TagAllInvoices).ToArrayAsync();
return await ctx.Apps.Where(a => a.StoreDataId == storeId && a.TagAllInvoices).ToArrayAsync();
}
} }
public async Task UpdateInvoice(string invoiceId, UpdateCustomerModel data) public async Task UpdateInvoice(string invoiceId, UpdateCustomerModel data)
{ {
using (var ctx = _applicationDbContextFactory.CreateContext()) using var ctx = _applicationDbContextFactory.CreateContext();
var invoiceData = await ctx.Invoices.FindAsync(invoiceId).ConfigureAwait(false);
if (invoiceData == null)
return;
if (invoiceData.CustomerEmail == null && data.Email != null)
{ {
var invoiceData = await ctx.Invoices.FindAsync(invoiceId).ConfigureAwait(false); invoiceData.CustomerEmail = data.Email;
if (invoiceData == null) AddToTextSearch(ctx, invoiceData, invoiceData.CustomerEmail);
return;
if (invoiceData.CustomerEmail == null && data.Email != null)
{
invoiceData.CustomerEmail = data.Email;
AddToTextSearch(ctx, invoiceData, invoiceData.CustomerEmail);
}
await ctx.SaveChangesAsync().ConfigureAwait(false);
} }
await ctx.SaveChangesAsync().ConfigureAwait(false);
} }
public async Task ExtendInvoiceMonitor(string invoiceId) public async Task ExtendInvoiceMonitor(string invoiceId)
{ {
using (var ctx = _applicationDbContextFactory.CreateContext()) using var ctx = _applicationDbContextFactory.CreateContext();
{ var invoiceData = await ctx.Invoices.FindAsync(invoiceId);
var invoiceData = await ctx.Invoices.FindAsync(invoiceId);
var invoice = invoiceData.GetBlob(_btcPayNetworkProvider); var invoice = invoiceData.GetBlob(_btcPayNetworkProvider);
invoice.MonitoringExpiration = invoice.MonitoringExpiration.AddHours(1); invoice.MonitoringExpiration = invoice.MonitoringExpiration.AddHours(1);
invoiceData.Blob = ToBytes(invoice, null); invoiceData.Blob = ToBytes(invoice, null);
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
}
} }
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, string[] additionalSearchTerms = null) public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, string[] additionalSearchTerms = null)
@ -316,50 +304,45 @@ namespace BTCPayServer.Services.Invoices
public async Task UpdateInvoicePaymentMethod(string invoiceId, PaymentMethod paymentMethod) public async Task UpdateInvoicePaymentMethod(string invoiceId, PaymentMethod paymentMethod)
{ {
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
var invoice = await context.Invoices.FindAsync(invoiceId);
if (invoice == null)
return;
var network = paymentMethod.Network;
var invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
var newDetails = paymentMethod.GetPaymentMethodDetails();
var existing = invoiceEntity.GetPaymentMethod(paymentMethod.GetId());
if (existing.GetPaymentMethodDetails().GetPaymentDestination() != newDetails.GetPaymentDestination() && newDetails.Activated)
{ {
var invoice = await context.Invoices.FindAsync(invoiceId); await context.AddressInvoices.AddAsync(new AddressInvoiceData()
if (invoice == null)
return;
var network = paymentMethod.Network;
var invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
var newDetails = paymentMethod.GetPaymentMethodDetails();
var existing = invoiceEntity.GetPaymentMethod(paymentMethod.GetId());
if (existing.GetPaymentMethodDetails().GetPaymentDestination() != newDetails.GetPaymentDestination() && newDetails.Activated)
{ {
await context.AddressInvoices.AddAsync(new AddressInvoiceData() InvoiceDataId = invoiceId,
{ CreatedTime = DateTimeOffset.UtcNow
InvoiceDataId = invoiceId,
CreatedTime = DateTimeOffset.UtcNow
}
.Set(GetDestination(paymentMethod), paymentMethod.GetId()));
await context.HistoricalAddressInvoices.AddAsync(new HistoricalAddressInvoiceData()
{
InvoiceDataId = invoiceId,
Assigned = DateTimeOffset.UtcNow
}.SetAddress(paymentMethod.GetPaymentMethodDetails().GetPaymentDestination(), network.CryptoCode));
} }
invoiceEntity.SetPaymentMethod(paymentMethod); .Set(GetDestination(paymentMethod), paymentMethod.GetId()));
invoice.Blob = ToBytes(invoiceEntity, network); await context.HistoricalAddressInvoices.AddAsync(new HistoricalAddressInvoiceData()
AddToTextSearch(context, invoice, paymentMethod.GetPaymentMethodDetails().GetPaymentDestination()); {
await context.SaveChangesAsync(); InvoiceDataId = invoiceId,
Assigned = DateTimeOffset.UtcNow
}.SetAddress(paymentMethod.GetPaymentMethodDetails().GetPaymentDestination(), network.CryptoCode));
} }
invoiceEntity.SetPaymentMethod(paymentMethod);
invoice.Blob = ToBytes(invoiceEntity, network);
AddToTextSearch(context, invoice, paymentMethod.GetPaymentMethodDetails().GetPaymentDestination());
await context.SaveChangesAsync();
} }
public async Task AddPendingInvoiceIfNotPresent(string invoiceId) public async Task AddPendingInvoiceIfNotPresent(string invoiceId)
{ {
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
if (!context.PendingInvoices.Any(a => a.Id == invoiceId))
{ {
if (!context.PendingInvoices.Any(a => a.Id == invoiceId)) context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoiceId });
try
{ {
context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoiceId }); await context.SaveChangesAsync();
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateException) { } // Already exists
} }
catch (DbUpdateException) { } // Already exists
} }
} }
@ -419,80 +402,70 @@ namespace BTCPayServer.Services.Invoices
public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState) public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState)
{ {
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
{ var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false); if (invoiceData == null)
if (invoiceData == null) return;
return; invoiceData.Status = InvoiceState.ToString(invoiceState.Status);
invoiceData.Status = InvoiceState.ToString(invoiceState.Status); invoiceData.ExceptionStatus = InvoiceState.ToString(invoiceState.ExceptionStatus);
invoiceData.ExceptionStatus = InvoiceState.ToString(invoiceState.ExceptionStatus); await context.SaveChangesAsync().ConfigureAwait(false);
await context.SaveChangesAsync().ConfigureAwait(false);
}
} }
internal async Task UpdateInvoicePrice(string invoiceId, InvoiceEntity invoice) internal async Task UpdateInvoicePrice(string invoiceId, InvoiceEntity invoice)
{ {
if (invoice.Type != InvoiceType.TopUp) if (invoice.Type != InvoiceType.TopUp)
throw new ArgumentException("The invoice type should be TopUp to be able to update invoice price", nameof(invoice)); throw new ArgumentException("The invoice type should be TopUp to be able to update invoice price", nameof(invoice));
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
{ var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false); if (invoiceData == null)
if (invoiceData == null) return;
return; var blob = invoiceData.GetBlob(_btcPayNetworkProvider);
var blob = invoiceData.GetBlob(_btcPayNetworkProvider); blob.Price = invoice.Price;
blob.Price = invoice.Price; AddToTextSearch(context, invoiceData, new[] { invoice.Price.ToString(CultureInfo.InvariantCulture) });
AddToTextSearch(context, invoiceData, new[] { invoice.Price.ToString(CultureInfo.InvariantCulture) }); invoiceData.Blob = ToBytes(blob, null);
invoiceData.Blob = ToBytes(blob, null); await context.SaveChangesAsync().ConfigureAwait(false);
await context.SaveChangesAsync().ConfigureAwait(false);
}
} }
public async Task MassArchive(string[] invoiceIds, bool archive = true) public async Task MassArchive(string[] invoiceIds, bool archive = true)
{ {
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
var items = context.Invoices.Where(a => invoiceIds.Contains(a.Id));
if (items == null)
{ {
var items = context.Invoices.Where(a => invoiceIds.Contains(a.Id)); return;
if (items == null)
{
return;
}
foreach (InvoiceData invoice in items)
{
invoice.Archived = archive;
}
await context.SaveChangesAsync();
} }
foreach (InvoiceData invoice in items)
{
invoice.Archived = archive;
}
await context.SaveChangesAsync();
} }
public async Task ToggleInvoiceArchival(string invoiceId, bool archived, string storeId = null) public async Task ToggleInvoiceArchival(string invoiceId, bool archived, string storeId = null)
{ {
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
{ var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false); if (invoiceData == null || invoiceData.Archived == archived ||
if (invoiceData == null || invoiceData.Archived == archived || (storeId != null &&
(storeId != null && !invoiceData.StoreDataId.Equals(storeId, StringComparison.InvariantCultureIgnoreCase)))
!invoiceData.StoreDataId.Equals(storeId, StringComparison.InvariantCultureIgnoreCase))) return;
return; invoiceData.Archived = archived;
invoiceData.Archived = archived; await context.SaveChangesAsync().ConfigureAwait(false);
await context.SaveChangesAsync().ConfigureAwait(false);
}
} }
public async Task<InvoiceEntity> UpdateInvoiceMetadata(string invoiceId, string storeId, JObject metadata) public async Task<InvoiceEntity> UpdateInvoiceMetadata(string invoiceId, string storeId, JObject metadata)
{ {
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
{ var invoiceData = await GetInvoiceRaw(invoiceId, context);
var invoiceData = await GetInvoiceRaw(invoiceId, context); if (invoiceData == null || (storeId != null &&
if (invoiceData == null || (storeId != null && !invoiceData.StoreDataId.Equals(storeId,
!invoiceData.StoreDataId.Equals(storeId, StringComparison.InvariantCultureIgnoreCase)))
StringComparison.InvariantCultureIgnoreCase))) return null;
return null; var blob = invoiceData.GetBlob(_btcPayNetworkProvider);
var blob = invoiceData.GetBlob(_btcPayNetworkProvider); blob.Metadata = InvoiceMetadata.FromJObject(metadata);
blob.Metadata = InvoiceMetadata.FromJObject(metadata); invoiceData.Blob = ToBytes(blob);
invoiceData.Blob = ToBytes(blob); await context.SaveChangesAsync().ConfigureAwait(false);
await context.SaveChangesAsync().ConfigureAwait(false); return ToEntity(invoiceData);
return ToEntity(invoiceData);
}
} }
public async Task<bool> MarkInvoiceStatus(string invoiceId, InvoiceStatus status) public async Task<bool> MarkInvoiceStatus(string invoiceId, InvoiceStatus status)
{ {
@ -541,25 +514,21 @@ namespace BTCPayServer.Services.Invoices
public async Task<InvoiceEntity> GetInvoice(string id, bool includeAddressData = false) public async Task<InvoiceEntity> GetInvoice(string id, bool includeAddressData = false)
{ {
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
{ var res = await GetInvoiceRaw(id, context, includeAddressData);
var res = await GetInvoiceRaw(id, context, includeAddressData); return res == null ? null : ToEntity(res);
return res == null ? null : ToEntity(res);
}
} }
public async Task<InvoiceEntity[]> GetInvoices(string[] invoiceIds) public async Task<InvoiceEntity[]> GetInvoices(string[] invoiceIds)
{ {
var invoiceIdSet = invoiceIds.ToHashSet(); var invoiceIdSet = invoiceIds.ToHashSet();
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
{ IQueryable<Data.InvoiceData> query =
IQueryable<Data.InvoiceData> query = context
context .Invoices
.Invoices .Include(o => o.Payments)
.Include(o => o.Payments) .Where(o => invoiceIdSet.Contains(o.Id));
.Where(o => invoiceIdSet.Contains(o.Id));
return (await query.ToListAsync()).Select(o => ToEntity(o)).ToArray(); return (await query.ToListAsync()).Select(o => ToEntity(o)).ToArray();
}
} }
private async Task<InvoiceData> GetInvoiceRaw(string id, ApplicationDbContext dbContext, bool includeAddressData = false) private async Task<InvoiceData> GetInvoiceRaw(string id, ApplicationDbContext dbContext, bool includeAddressData = false)
@ -710,26 +679,22 @@ namespace BTCPayServer.Services.Invoices
public async Task<int> GetInvoicesTotal(InvoiceQuery queryObject) public async Task<int> GetInvoicesTotal(InvoiceQuery queryObject)
{ {
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
{ var query = GetInvoiceQuery(context, queryObject);
var query = GetInvoiceQuery(context, queryObject); return await query.CountAsync();
return await query.CountAsync();
}
} }
public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject) public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject)
{ {
using (var context = _applicationDbContextFactory.CreateContext()) using var context = _applicationDbContextFactory.CreateContext();
{ var query = GetInvoiceQuery(context, queryObject);
var query = GetInvoiceQuery(context, queryObject); query = query.Include(o => o.Payments);
query = query.Include(o => o.Payments); if (queryObject.IncludeAddresses)
if (queryObject.IncludeAddresses) query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices);
query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices); if (queryObject.IncludeEvents)
if (queryObject.IncludeEvents) query = query.Include(o => o.Events);
query = query.Include(o => o.Events); var data = await query.ToArrayAsync().ConfigureAwait(false);
var data = await query.ToArrayAsync().ConfigureAwait(false); return data.Select(ToEntity).ToArray();
return data.Select(ToEntity).ToArray();
}
} }
private string NormalizeExceptionStatus(string status) private string NormalizeExceptionStatus(string status)

View File

@ -37,11 +37,9 @@ namespace BTCPayServer.Services
var result = new List<Language>(); var result = new List<Language>();
foreach (var file in files) foreach (var file in files)
{ {
using (var stream = new StreamReader(file)) using var stream = new StreamReader(file);
{ var json = stream.ReadToEnd();
var json = stream.ReadToEnd(); result.Add(JObject.Parse(json).ToObject<Language>());
result.Add(JObject.Parse(json).ToObject<Language>());
}
} }
_languages = result.ToArray(); _languages = result.ToArray();

View File

@ -30,12 +30,10 @@ namespace BTCPayServer.Services.Mails
Logs.Configuration.LogWarning("Should have sent email, but email settings are not configured"); Logs.Configuration.LogWarning("Should have sent email, but email settings are not configured");
return; return;
} }
using (var smtp = await emailSettings.CreateSmtpClient()) using var smtp = await emailSettings.CreateSmtpClient();
{ var mail = emailSettings.CreateMailMessage(new MailboxAddress(email, email), subject, message, true);
var mail = emailSettings.CreateMailMessage(new MailboxAddress(email, email), subject, message, true); await smtp.SendAsync(mail, cancellationToken);
await smtp.SendAsync(mail, cancellationToken); await smtp.DisconnectAsync(true, cancellationToken);
await smtp.DisconnectAsync(true, cancellationToken);
}
}, TimeSpan.Zero); }, TimeSpan.Zero);
} }

View File

@ -22,21 +22,19 @@ namespace BTCPayServer.Services.PaymentRequests
public async Task<PaymentRequestData> CreateOrUpdatePaymentRequest(PaymentRequestData entity) public async Task<PaymentRequestData> CreateOrUpdatePaymentRequest(PaymentRequestData entity)
{ {
using (var context = _ContextFactory.CreateContext()) using var context = _ContextFactory.CreateContext();
if (string.IsNullOrEmpty(entity.Id))
{ {
if (string.IsNullOrEmpty(entity.Id)) entity.Id = Guid.NewGuid().ToString();
{ await context.PaymentRequests.AddAsync(entity);
entity.Id = Guid.NewGuid().ToString();
await context.PaymentRequests.AddAsync(entity);
}
else
{
context.PaymentRequests.Update(entity);
}
await context.SaveChangesAsync();
return entity;
} }
else
{
context.PaymentRequests.Update(entity);
}
await context.SaveChangesAsync();
return entity;
} }
public async Task<PaymentRequestData> FindPaymentRequest(string id, string userId, CancellationToken cancellationToken = default) public async Task<PaymentRequestData> FindPaymentRequest(string id, string userId, CancellationToken cancellationToken = default)
@ -46,15 +44,13 @@ namespace BTCPayServer.Services.PaymentRequests
return null; return null;
} }
using (var context = _ContextFactory.CreateContext()) using var context = _ContextFactory.CreateContext();
{ var result = await context.PaymentRequests.Include(x => x.StoreData)
var result = await context.PaymentRequests.Include(x => x.StoreData) .Where(data =>
.Where(data => string.IsNullOrEmpty(userId) ||
string.IsNullOrEmpty(userId) || (data.StoreData != null && data.StoreData.UserStores.Any(u => u.ApplicationUserId == userId)))
(data.StoreData != null && data.StoreData.UserStores.Any(u => u.ApplicationUserId == userId))) .SingleOrDefaultAsync(x => x.Id == id, cancellationToken);
.SingleOrDefaultAsync(x => x.Id == id, cancellationToken); return result;
return result;
}
} }
public async Task<bool> IsPaymentRequestAdmin(string paymentRequestId, string userId) public async Task<bool> IsPaymentRequestAdmin(string paymentRequestId, string userId)
@ -63,76 +59,70 @@ namespace BTCPayServer.Services.PaymentRequests
{ {
return false; return false;
} }
using (var context = _ContextFactory.CreateContext()) using var context = _ContextFactory.CreateContext();
{ return await context.PaymentRequests.Include(x => x.StoreData)
return await context.PaymentRequests.Include(x => x.StoreData) .AnyAsync(data =>
.AnyAsync(data => data.Id == paymentRequestId &&
data.Id == paymentRequestId && (data.StoreData != null && data.StoreData.UserStores.Any(u => u.ApplicationUserId == userId)));
(data.StoreData != null && data.StoreData.UserStores.Any(u => u.ApplicationUserId == userId)));
}
} }
public async Task UpdatePaymentRequestStatus(string paymentRequestId, Client.Models.PaymentRequestData.PaymentRequestStatus status, CancellationToken cancellationToken = default) public async Task UpdatePaymentRequestStatus(string paymentRequestId, Client.Models.PaymentRequestData.PaymentRequestStatus status, CancellationToken cancellationToken = default)
{ {
using (var context = _ContextFactory.CreateContext()) using var context = _ContextFactory.CreateContext();
{ var invoiceData = await context.FindAsync<PaymentRequestData>(paymentRequestId);
var invoiceData = await context.FindAsync<PaymentRequestData>(paymentRequestId); if (invoiceData == null)
if (invoiceData == null) return;
return; invoiceData.Status = status;
invoiceData.Status = status; await context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
} }
public async Task<(int Total, PaymentRequestData[] Items)> FindPaymentRequests(PaymentRequestQuery query, CancellationToken cancellationToken = default) public async Task<(int Total, PaymentRequestData[] Items)> FindPaymentRequests(PaymentRequestQuery query, CancellationToken cancellationToken = default)
{ {
using (var context = _ContextFactory.CreateContext()) using var context = _ContextFactory.CreateContext();
var queryable = context.PaymentRequests.Include(data => data.StoreData).AsQueryable();
if (!query.IncludeArchived)
{ {
var queryable = context.PaymentRequests.Include(data => data.StoreData).AsQueryable(); queryable = queryable.Where(data => !data.Archived);
if (!query.IncludeArchived)
{
queryable = queryable.Where(data => !data.Archived);
}
if (!string.IsNullOrEmpty(query.StoreId))
{
queryable = queryable.Where(data =>
data.StoreDataId == query.StoreId);
}
if (query.Status != null && query.Status.Any())
{
queryable = queryable.Where(data =>
query.Status.Contains(data.Status));
}
if (query.Ids != null && query.Ids.Any())
{
queryable = queryable.Where(data =>
query.Ids.Contains(data.Id));
}
if (!string.IsNullOrEmpty(query.UserId))
{
queryable = queryable.Where(i =>
i.StoreData != null && i.StoreData.UserStores.Any(u => u.ApplicationUserId == query.UserId));
}
var total = await queryable.CountAsync(cancellationToken);
queryable = queryable.OrderByDescending(u => u.Created);
if (query.Skip.HasValue)
{
queryable = queryable.Skip(query.Skip.Value);
}
if (query.Count.HasValue)
{
queryable = queryable.Take(query.Count.Value);
}
return (total, await queryable.ToArrayAsync(cancellationToken));
} }
if (!string.IsNullOrEmpty(query.StoreId))
{
queryable = queryable.Where(data =>
data.StoreDataId == query.StoreId);
}
if (query.Status != null && query.Status.Any())
{
queryable = queryable.Where(data =>
query.Status.Contains(data.Status));
}
if (query.Ids != null && query.Ids.Any())
{
queryable = queryable.Where(data =>
query.Ids.Contains(data.Id));
}
if (!string.IsNullOrEmpty(query.UserId))
{
queryable = queryable.Where(i =>
i.StoreData != null && i.StoreData.UserStores.Any(u => u.ApplicationUserId == query.UserId));
}
var total = await queryable.CountAsync(cancellationToken);
queryable = queryable.OrderByDescending(u => u.Created);
if (query.Skip.HasValue)
{
queryable = queryable.Skip(query.Skip.Value);
}
if (query.Count.HasValue)
{
queryable = queryable.Take(query.Count.Value);
}
return (total, await queryable.ToArrayAsync(cancellationToken));
} }
public async Task<InvoiceEntity[]> GetInvoicesForPaymentRequest(string paymentRequestId, public async Task<InvoiceEntity[]> GetInvoicesForPaymentRequest(string paymentRequestId,

View File

@ -26,11 +26,9 @@ namespace BTCPayServer.Services.Stores
{ {
if (storeId == null) if (storeId == null)
return null; return null;
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ var result = await ctx.FindAsync<StoreData>(storeId).ConfigureAwait(false);
var result = await ctx.FindAsync<StoreData>(storeId).ConfigureAwait(false); return result;
return result;
}
} }
public async Task<StoreData> FindStore(string storeId, string userId) public async Task<StoreData> FindStore(string storeId, string userId)
@ -62,34 +60,30 @@ namespace BTCPayServer.Services.Stores
public async Task<StoreUser[]> GetStoreUsers(string storeId) public async Task<StoreUser[]> GetStoreUsers(string storeId)
{ {
ArgumentNullException.ThrowIfNull(storeId); ArgumentNullException.ThrowIfNull(storeId);
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ return await ctx
return await ctx .UserStore
.UserStore .Where(u => u.StoreDataId == storeId)
.Where(u => u.StoreDataId == storeId) .Select(u => new StoreUser()
.Select(u => new StoreUser() {
{ Id = u.ApplicationUserId,
Id = u.ApplicationUserId, Email = u.ApplicationUser.Email,
Email = u.ApplicationUser.Email, Role = u.Role
Role = u.Role }).ToArrayAsync();
}).ToArrayAsync();
}
} }
public async Task<StoreData[]> GetStoresByUserId(string userId, IEnumerable<string> storeIds = null) public async Task<StoreData[]> GetStoresByUserId(string userId, IEnumerable<string> storeIds = null)
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ return (await ctx.UserStore
return (await ctx.UserStore .Where(u => u.ApplicationUserId == userId && (storeIds == null || storeIds.Contains(u.StoreDataId)))
.Where(u => u.ApplicationUserId == userId && (storeIds == null || storeIds.Contains(u.StoreDataId))) .Select(u => new { u.StoreData, u.Role })
.Select(u => new { u.StoreData, u.Role }) .ToArrayAsync())
.ToArrayAsync()) .Select(u =>
.Select(u => {
{ u.StoreData.Role = u.Role;
u.StoreData.Role = u.Role; return u.StoreData;
return u.StoreData; }).ToArray();
}).ToArray();
}
} }
public async Task<StoreData> GetStoreByInvoiceId(string invoiceId) public async Task<StoreData> GetStoreByInvoiceId(string invoiceId)
@ -102,34 +96,30 @@ namespace BTCPayServer.Services.Stores
public async Task<bool> AddStoreUser(string storeId, string userId, string role) public async Task<bool> AddStoreUser(string storeId, string userId, string role)
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
var userStore = new UserStore() { StoreDataId = storeId, ApplicationUserId = userId, Role = role };
ctx.UserStore.Add(userStore);
try
{ {
var userStore = new UserStore() { StoreDataId = storeId, ApplicationUserId = userId, Role = role }; await ctx.SaveChangesAsync();
ctx.UserStore.Add(userStore); return true;
try }
{ catch (Microsoft.EntityFrameworkCore.DbUpdateException)
await ctx.SaveChangesAsync(); {
return true; return false;
}
catch (Microsoft.EntityFrameworkCore.DbUpdateException)
{
return false;
}
} }
} }
public async Task CleanUnreachableStores() public async Task CleanUnreachableStores()
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
if (!ctx.Database.SupportDropForeignKey())
return;
foreach (var store in await ctx.Stores.Where(s => !s.UserStores.Where(u => u.Role == StoreRoles.Owner).Any()).ToArrayAsync())
{ {
if (!ctx.Database.SupportDropForeignKey()) ctx.Stores.Remove(store);
return;
foreach (var store in await ctx.Stores.Where(s => !s.UserStores.Where(u => u.Role == StoreRoles.Owner).Any()).ToArrayAsync())
{
ctx.Stores.Remove(store);
}
await ctx.SaveChangesAsync();
} }
await ctx.SaveChangesAsync();
} }
public async Task RemoveStoreUser(string storeId, string userId) public async Task RemoveStoreUser(string storeId, string userId)
@ -147,18 +137,16 @@ namespace BTCPayServer.Services.Stores
private async Task DeleteStoreIfOrphan(string storeId) private async Task DeleteStoreIfOrphan(string storeId)
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
if (ctx.Database.SupportDropForeignKey())
{ {
if (ctx.Database.SupportDropForeignKey()) if (!await ctx.UserStore.Where(u => u.StoreDataId == storeId && u.Role == StoreRoles.Owner).AnyAsync())
{ {
if (!await ctx.UserStore.Where(u => u.StoreDataId == storeId && u.Role == StoreRoles.Owner).AnyAsync()) var store = await ctx.Stores.FindAsync(storeId);
if (store != null)
{ {
var store = await ctx.Stores.FindAsync(storeId); ctx.Stores.Remove(store);
if (store != null) await ctx.SaveChangesAsync();
{
ctx.Stores.Remove(store);
await ctx.SaveChangesAsync();
}
} }
} }
} }
@ -182,22 +170,20 @@ namespace BTCPayServer.Services.Stores
if (string.IsNullOrEmpty(storeData.StoreName)) if (string.IsNullOrEmpty(storeData.StoreName))
throw new ArgumentException("name should not be empty", nameof(storeData.StoreName)); throw new ArgumentException("name should not be empty", nameof(storeData.StoreName));
ArgumentNullException.ThrowIfNull(ownerId); ArgumentNullException.ThrowIfNull(ownerId);
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
storeData.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32));
var userStore = new UserStore
{ {
storeData.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32)); StoreDataId = storeData.Id,
var userStore = new UserStore ApplicationUserId = ownerId,
{ Role = StoreRoles.Owner,
StoreDataId = storeData.Id, };
ApplicationUserId = ownerId,
Role = StoreRoles.Owner,
};
SetNewStoreHints(ref storeData); SetNewStoreHints(ref storeData);
ctx.Add(storeData); ctx.Add(storeData);
ctx.Add(userStore); ctx.Add(userStore);
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
}
} }
public async Task<StoreData> CreateStore(string ownerId, string name) public async Task<StoreData> CreateStore(string ownerId, string name)
@ -343,41 +329,35 @@ namespace BTCPayServer.Services.Stores
public async Task UpdateStore(StoreData store) public async Task UpdateStore(StoreData store)
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ var existing = await ctx.FindAsync<StoreData>(store.Id);
var existing = await ctx.FindAsync<StoreData>(store.Id); ctx.Entry(existing).CurrentValues.SetValues(store);
ctx.Entry(existing).CurrentValues.SetValues(store); await ctx.SaveChangesAsync().ConfigureAwait(false);
await ctx.SaveChangesAsync().ConfigureAwait(false);
}
} }
public async Task<bool> DeleteStore(string storeId) public async Task<bool> DeleteStore(string storeId)
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ if (!ctx.Database.SupportDropForeignKey())
if (!ctx.Database.SupportDropForeignKey()) return false;
return false; var store = await ctx.Stores.FindAsync(storeId);
var store = await ctx.Stores.FindAsync(storeId); if (store == null)
if (store == null) return false;
return false; var webhooks = await ctx.StoreWebhooks
var webhooks = await ctx.StoreWebhooks .Where(o => o.StoreId == storeId)
.Where(o => o.StoreId == storeId) .Select(o => o.Webhook)
.Select(o => o.Webhook) .ToArrayAsync();
.ToArrayAsync(); foreach (var w in webhooks)
foreach (var w in webhooks) ctx.Webhooks.Remove(w);
ctx.Webhooks.Remove(w); ctx.Stores.Remove(store);
ctx.Stores.Remove(store); await ctx.SaveChangesAsync();
await ctx.SaveChangesAsync(); return true;
return true;
}
} }
public bool CanDeleteStores() public bool CanDeleteStores()
{ {
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ return ctx.Database.SupportDropForeignKey();
return ctx.Database.SupportDropForeignKey();
}
} }
} }
} }

View File

@ -19,77 +19,69 @@ namespace BTCPayServer.Services
public async Task SetWalletInfo(WalletId walletId, WalletBlobInfo blob) public async Task SetWalletInfo(WalletId walletId, WalletBlobInfo blob)
{ {
ArgumentNullException.ThrowIfNull(walletId); ArgumentNullException.ThrowIfNull(walletId);
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
var walletData = new WalletData() { Id = walletId.ToString() };
walletData.SetBlobInfo(blob);
var entity = await ctx.Wallets.AddAsync(walletData);
entity.State = EntityState.Modified;
try
{ {
var walletData = new WalletData() { Id = walletId.ToString() }; await ctx.SaveChangesAsync();
walletData.SetBlobInfo(blob); }
var entity = await ctx.Wallets.AddAsync(walletData); catch (DbUpdateException) // Does not exists
entity.State = EntityState.Modified; {
try entity.State = EntityState.Added;
{ await ctx.SaveChangesAsync();
await ctx.SaveChangesAsync();
}
catch (DbUpdateException) // Does not exists
{
entity.State = EntityState.Added;
await ctx.SaveChangesAsync();
}
} }
} }
public async Task<Dictionary<string, WalletTransactionInfo>> GetWalletTransactionsInfo(WalletId walletId, string[] transactionIds = null) public async Task<Dictionary<string, WalletTransactionInfo>> GetWalletTransactionsInfo(WalletId walletId, string[] transactionIds = null)
{ {
ArgumentNullException.ThrowIfNull(walletId); ArgumentNullException.ThrowIfNull(walletId);
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ return (await ctx.WalletTransactions
return (await ctx.WalletTransactions .Where(w => w.WalletDataId == walletId.ToString())
.Where(w => w.WalletDataId == walletId.ToString()) .Where(data => transactionIds == null || transactionIds.Contains(data.TransactionId))
.Where(data => transactionIds == null || transactionIds.Contains(data.TransactionId)) .Select(w => w)
.Select(w => w) .ToArrayAsync())
.ToArrayAsync()) .ToDictionary(w => w.TransactionId, w => w.GetBlobInfo());
.ToDictionary(w => w.TransactionId, w => w.GetBlobInfo());
}
} }
public async Task<WalletBlobInfo> GetWalletInfo(WalletId walletId) public async Task<WalletBlobInfo> GetWalletInfo(WalletId walletId)
{ {
ArgumentNullException.ThrowIfNull(walletId); ArgumentNullException.ThrowIfNull(walletId);
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
{ var data = await ctx.Wallets
var data = await ctx.Wallets .Where(w => w.Id == walletId.ToString())
.Where(w => w.Id == walletId.ToString()) .Select(w => w)
.Select(w => w) .FirstOrDefaultAsync();
.FirstOrDefaultAsync(); return data?.GetBlobInfo() ?? new WalletBlobInfo();
return data?.GetBlobInfo() ?? new WalletBlobInfo();
}
} }
public async Task SetWalletTransactionInfo(WalletId walletId, string transactionId, WalletTransactionInfo walletTransactionInfo) public async Task SetWalletTransactionInfo(WalletId walletId, string transactionId, WalletTransactionInfo walletTransactionInfo)
{ {
ArgumentNullException.ThrowIfNull(walletId); ArgumentNullException.ThrowIfNull(walletId);
ArgumentNullException.ThrowIfNull(transactionId); ArgumentNullException.ThrowIfNull(transactionId);
using (var ctx = _ContextFactory.CreateContext()) using var ctx = _ContextFactory.CreateContext();
var walletData = new WalletTransactionData() { WalletDataId = walletId.ToString(), TransactionId = transactionId };
walletData.SetBlobInfo(walletTransactionInfo);
var entity = await ctx.WalletTransactions.AddAsync(walletData);
entity.State = EntityState.Modified;
try
{ {
var walletData = new WalletTransactionData() { WalletDataId = walletId.ToString(), TransactionId = transactionId }; await ctx.SaveChangesAsync();
walletData.SetBlobInfo(walletTransactionInfo); }
var entity = await ctx.WalletTransactions.AddAsync(walletData); catch (DbUpdateException) // Does not exists
entity.State = EntityState.Modified; {
entity.State = EntityState.Added;
try try
{ {
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
catch (DbUpdateException) // Does not exists catch (DbUpdateException) // the Wallet does not exists in the DB
{ {
entity.State = EntityState.Added; await SetWalletInfo(walletId, new WalletBlobInfo());
try await ctx.SaveChangesAsync();
{
await ctx.SaveChangesAsync();
}
catch (DbUpdateException) // the Wallet does not exists in the DB
{
await SetWalletInfo(walletId, new WalletBlobInfo());
await ctx.SaveChangesAsync();
}
} }
} }
} }

View File

@ -29,35 +29,29 @@ namespace BTCPayServer.Storage.Services
filesQuery = new FilesQuery(); filesQuery = new FilesQuery();
} }
using (var context = _ApplicationDbContextFactory.CreateContext()) using var context = _ApplicationDbContextFactory.CreateContext();
{ return await context.Files
return await context.Files .Include(file => file.ApplicationUser)
.Include(file => file.ApplicationUser) .Where(file =>
.Where(file => (!filesQuery.Id.Any() || filesQuery.Id.Contains(file.Id)) &&
(!filesQuery.Id.Any() || filesQuery.Id.Contains(file.Id)) && (!filesQuery.UserIds.Any() || filesQuery.UserIds.Contains(file.ApplicationUserId)))
(!filesQuery.UserIds.Any() || filesQuery.UserIds.Contains(file.ApplicationUserId))) .OrderByDescending(file => file.Timestamp)
.OrderByDescending(file => file.Timestamp) .ToListAsync();
.ToListAsync();
}
} }
public async Task RemoveFile(StoredFile file) public async Task RemoveFile(StoredFile file)
{ {
using (var context = _ApplicationDbContextFactory.CreateContext()) using var context = _ApplicationDbContextFactory.CreateContext();
{ context.Attach(file);
context.Attach(file); context.Files.Remove(file);
context.Files.Remove(file); await context.SaveChangesAsync();
await context.SaveChangesAsync();
}
} }
public async Task AddFile(StoredFile storedFile) public async Task AddFile(StoredFile storedFile)
{ {
using (var context = _ApplicationDbContextFactory.CreateContext()) using var context = _ApplicationDbContextFactory.CreateContext();
{ await context.AddAsync(storedFile);
await context.AddAsync(storedFile); await context.SaveChangesAsync();
await context.SaveChangesAsync();
}
} }
public class FilesQuery public class FilesQuery

View File

@ -94,13 +94,9 @@ namespace BTCPayServer
public async Task Send(string evt, CancellationToken cancellation = default) public async Task Send(string evt, CancellationToken cancellation = default)
{ {
var bytes = UTF8.GetBytes(evt); var bytes = UTF8.GetBytes(evt);
using (var cts = new CancellationTokenSource(5000)) using var cts = new CancellationTokenSource(5000);
{ using var cts2 = CancellationTokenSource.CreateLinkedTokenSource(cancellation);
using (var cts2 = CancellationTokenSource.CreateLinkedTokenSource(cancellation)) await Socket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, cts2.Token);
{
await Socket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, cts2.Token);
}
}
} }
public async Task DisposeAsync(CancellationToken cancellation) public async Task DisposeAsync(CancellationToken cancellation)