mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
Warning if not using 'simple using'
This commit is contained in:
parent
c6a7e90c1a
commit
50d4b55f73
@ -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
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
@ -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);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
@ -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
@ -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
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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)
|
||||||
|
@ -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 { }
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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 _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user