mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-19 15:04:19 +01:00
Lightning address support (#2804)
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
@@ -3,6 +3,8 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Security.Authentication;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -216,14 +218,14 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
// We should be logged in now
|
// We should be logged in now
|
||||||
s.Driver.FindElement(By.Id("mainNav"));
|
s.Driver.FindElement(By.Id("mainNav"));
|
||||||
|
|
||||||
//let's test delete user quickly while we're at it
|
//let's test delete user quickly while we're at it
|
||||||
s.GoToProfile();
|
s.GoToProfile();
|
||||||
s.Driver.FindElement(By.Id("danger-zone-expander")).Click();
|
s.Driver.FindElement(By.Id("danger-zone-expander")).Click();
|
||||||
s.Driver.FindElement(By.Id("delete-user")).Click();
|
s.Driver.FindElement(By.Id("delete-user")).Click();
|
||||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||||
|
|
||||||
Assert.Contains("/login", s.Driver.Url);
|
Assert.Contains("/login", s.Driver.Url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,13 +243,15 @@ namespace BTCPayServer.Tests
|
|||||||
s.RegisterNewUser(isAdmin: true);
|
s.RegisterNewUser(isAdmin: true);
|
||||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
|
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
|
||||||
Assert.Contains("server/services/ssh", s.Driver.PageSource);
|
Assert.Contains("server/services/ssh", s.Driver.PageSource);
|
||||||
using (var client = await s.Server.PayTester.GetService<Configuration.BTCPayServerOptions>().SSHSettings.ConnectAsync())
|
using (var client = await s.Server.PayTester.GetService<Configuration.BTCPayServerOptions>().SSHSettings
|
||||||
|
.ConnectAsync())
|
||||||
{
|
{
|
||||||
var result = await client.RunBash("echo hello");
|
var result = await client.RunBash("echo hello");
|
||||||
Assert.Equal(string.Empty, result.Error);
|
Assert.Equal(string.Empty, result.Error);
|
||||||
Assert.Equal("hello\n", result.Output);
|
Assert.Equal("hello\n", result.Output);
|
||||||
Assert.Equal(0, result.ExitStatus);
|
Assert.Equal(0, result.ExitStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
||||||
s.Driver.AssertNoError();
|
s.Driver.AssertNoError();
|
||||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
|
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
|
||||||
@@ -259,7 +263,8 @@ namespace BTCPayServer.Tests
|
|||||||
// Browser replace \n to \r\n, so it is hard to compare exactly what we want
|
// Browser replace \n to \r\n, so it is hard to compare exactly what we want
|
||||||
Assert.Contains("tes't", text);
|
Assert.Contains("tes't", text);
|
||||||
Assert.Contains("test2", text);
|
Assert.Contains("test2", text);
|
||||||
Assert.True(s.Driver.PageSource.Contains("authorized_keys has been updated", StringComparison.OrdinalIgnoreCase));
|
Assert.True(s.Driver.PageSource.Contains("authorized_keys has been updated",
|
||||||
|
StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
|
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
|
||||||
s.Driver.FindElement(By.Id("submit")).Click();
|
s.Driver.FindElement(By.Id("submit")).Click();
|
||||||
@@ -273,10 +278,10 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
||||||
Assert.True(s.Driver.PageSource.Contains("404 - Page not found", StringComparison.OrdinalIgnoreCase));
|
Assert.True(s.Driver.PageSource.Contains("404 - Page not found", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
policies = await settings.GetSettingAsync<PoliciesSettings>();
|
policies = await settings.GetSettingAsync<PoliciesSettings>();
|
||||||
Assert.True(policies.DisableSSHService);
|
Assert.True(policies.DisableSSHService);
|
||||||
|
|
||||||
policies.DisableSSHService = false;
|
policies.DisableSSHService = false;
|
||||||
await settings.UpdateSetting(policies);
|
await settings.UpdateSetting(policies);
|
||||||
}
|
}
|
||||||
@@ -295,6 +300,7 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
|
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
|
||||||
s.FindAlertMessage();
|
s.FindAlertMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
CanSetupEmailCore(s);
|
CanSetupEmailCore(s);
|
||||||
s.CreateNewStore();
|
s.CreateNewStore();
|
||||||
s.GoToUrl($"stores/{s.StoreId}/emails");
|
s.GoToUrl($"stores/{s.StoreId}/emails");
|
||||||
@@ -320,6 +326,7 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
|
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
|
||||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("AddDynamicDNS")).Click();
|
s.Driver.FindElement(By.Id("AddDynamicDNS")).Click();
|
||||||
s.Driver.AssertNoError();
|
s.Driver.AssertNoError();
|
||||||
// We will just cheat for test purposes by only querying the server
|
// We will just cheat for test purposes by only querying the server
|
||||||
@@ -375,13 +382,15 @@ namespace BTCPayServer.Tests
|
|||||||
s.GoToStore(storeId);
|
s.GoToStore(storeId);
|
||||||
Assert.Contains(storeName, s.Driver.PageSource);
|
Assert.Contains(storeName, s.Driver.PageSource);
|
||||||
Assert.True(s.Driver.PageSource.Contains(onchainHint), "Wallet hint should be present at this point");
|
Assert.True(s.Driver.PageSource.Contains(onchainHint), "Wallet hint should be present at this point");
|
||||||
Assert.True(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be present at this point");
|
Assert.True(s.Driver.PageSource.Contains(offchainHint),
|
||||||
|
"Lightning hint should be present at this point");
|
||||||
|
|
||||||
// setup onchain wallet
|
// setup onchain wallet
|
||||||
s.GoToStore(storeId);
|
s.GoToStore(storeId);
|
||||||
s.AddDerivationScheme();
|
s.AddDerivationScheme();
|
||||||
s.Driver.AssertNoError();
|
s.Driver.AssertNoError();
|
||||||
Assert.False(s.Driver.PageSource.Contains(onchainHint), "Wallet hint not dismissed on derivation scheme add");
|
Assert.False(s.Driver.PageSource.Contains(onchainHint),
|
||||||
|
"Wallet hint not dismissed on derivation scheme add");
|
||||||
|
|
||||||
// setup offchain wallet
|
// setup offchain wallet
|
||||||
s.GoToStore(storeId);
|
s.GoToStore(storeId);
|
||||||
@@ -389,7 +398,8 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.AssertNoError();
|
s.Driver.AssertNoError();
|
||||||
var successAlert = s.FindAlertMessage();
|
var successAlert = s.FindAlertMessage();
|
||||||
Assert.Contains("BTC Lightning node updated.", successAlert.Text);
|
Assert.Contains("BTC Lightning node updated.", successAlert.Text);
|
||||||
Assert.False(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be dismissed at this point");
|
Assert.False(s.Driver.PageSource.Contains(offchainHint),
|
||||||
|
"Lightning hint should be dismissed at this point");
|
||||||
|
|
||||||
var storeUrl = s.Driver.Url;
|
var storeUrl = s.Driver.Url;
|
||||||
s.ClickOnAllSideMenus();
|
s.ClickOnAllSideMenus();
|
||||||
@@ -482,12 +492,9 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
var client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
|
var client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
|
||||||
await client.AuthorizeClient(new NBitpayClient.PairingCode(pairingCode));
|
await client.AuthorizeClient(new NBitpayClient.PairingCode(pairingCode));
|
||||||
await client.CreateInvoiceAsync(new NBitpayClient.Invoice()
|
await client.CreateInvoiceAsync(
|
||||||
{
|
new NBitpayClient.Invoice() { Price = 0.000000012m, Currency = "USD", FullNotifications = true },
|
||||||
Price = 0.000000012m,
|
NBitpayClient.Facade.Merchant);
|
||||||
Currency = "USD",
|
|
||||||
FullNotifications = true
|
|
||||||
}, NBitpayClient.Facade.Merchant);
|
|
||||||
|
|
||||||
client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
|
client = new NBitpayClient.Bitpay(new Key(), s.ServerUri);
|
||||||
|
|
||||||
@@ -495,12 +502,9 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.Navigate().GoToUrl(code.CreateLink(s.ServerUri));
|
s.Driver.Navigate().GoToUrl(code.CreateLink(s.ServerUri));
|
||||||
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
|
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
|
||||||
|
|
||||||
await client.CreateInvoiceAsync(new NBitpayClient.Invoice()
|
await client.CreateInvoiceAsync(
|
||||||
{
|
new NBitpayClient.Invoice() { Price = 0.000000012m, Currency = "USD", FullNotifications = true },
|
||||||
Price = 0.000000012m,
|
NBitpayClient.Facade.Merchant);
|
||||||
Currency = "USD",
|
|
||||||
FullNotifications = true
|
|
||||||
}, NBitpayClient.Facade.Merchant);
|
|
||||||
|
|
||||||
s.Driver.Navigate().GoToUrl(s.Link("/api-tokens"));
|
s.Driver.Navigate().GoToUrl(s.Link("/api-tokens"));
|
||||||
s.Driver.FindElement(By.Id("RequestPairing")).Click();
|
s.Driver.FindElement(By.Id("RequestPairing")).Click();
|
||||||
@@ -571,7 +575,8 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700");
|
s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700");
|
||||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||||
Assert.Equal("currently active!", s.Driver.FindElement(By.CssSelector("[data-test='time-state']")).Text);
|
Assert.Equal("currently active!",
|
||||||
|
s.Driver.FindElement(By.CssSelector("[data-test='time-state']")).Text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,8 +599,9 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.Name("ViewAppButton")).Click();
|
s.Driver.FindElement(By.Name("ViewAppButton")).Click();
|
||||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text);
|
Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text);
|
||||||
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
Assert.Equal("Pay Invoice",
|
||||||
|
s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
||||||
|
|
||||||
// expire
|
// expire
|
||||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
s.Driver.ExecuteJavaScript("document.getElementById('ExpiryDate').value = '2021-01-21T21:00:00.000Z'");
|
s.Driver.ExecuteJavaScript("document.getElementById('ExpiryDate').value = '2021-01-21T21:00:00.000Z'");
|
||||||
@@ -603,7 +609,7 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
s.Driver.Navigate().Refresh();
|
s.Driver.Navigate().Refresh();
|
||||||
Assert.Equal("Expired", s.Driver.WaitForElement(By.CssSelector("[data-test='status']")).Text);
|
Assert.Equal("Expired", s.Driver.WaitForElement(By.CssSelector("[data-test='status']")).Text);
|
||||||
|
|
||||||
// unexpire
|
// unexpire
|
||||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
s.Driver.FindElement(By.Id("ClearExpiryDate")).Click();
|
s.Driver.FindElement(By.Id("ClearExpiryDate")).Click();
|
||||||
@@ -611,7 +617,8 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
s.Driver.Navigate().Refresh();
|
s.Driver.Navigate().Refresh();
|
||||||
s.Driver.AssertElementNotFound(By.CssSelector("[data-test='status']"));
|
s.Driver.AssertElementNotFound(By.CssSelector("[data-test='status']"));
|
||||||
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
Assert.Equal("Pay Invoice",
|
||||||
|
s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,15 +635,18 @@ namespace BTCPayServer.Tests
|
|||||||
s.GoToWallet(walletId, WalletsNavPages.Receive);
|
s.GoToWallet(walletId, WalletsNavPages.Receive);
|
||||||
s.Driver.FindElement(By.Id("generateButton")).Click();
|
s.Driver.FindElement(By.Id("generateButton")).Click();
|
||||||
var addressStr = s.Driver.FindElement(By.Id("address")).GetProperty("value");
|
var addressStr = s.Driver.FindElement(By.Id("address")).GetProperty("value");
|
||||||
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)s.Server.NetworkProvider.GetNetwork("BTC")).NBitcoinNetwork);
|
var address = BitcoinAddress.Create(addressStr,
|
||||||
|
((BTCPayNetwork)s.Server.NetworkProvider.GetNetwork("BTC")).NBitcoinNetwork);
|
||||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||||
for (int i = 0; i < 6; i++)
|
for (int i = 0; i < 6; i++)
|
||||||
{
|
{
|
||||||
await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.0m));
|
await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.0m));
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetTx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.2m));
|
var targetTx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.2m));
|
||||||
var tx = await s.Server.ExplorerNode.GetRawTransactionAsync(targetTx);
|
var tx = await s.Server.ExplorerNode.GetRawTransactionAsync(targetTx);
|
||||||
var spentOutpoint = new OutPoint(targetTx, tx.Outputs.FindIndex(txout => txout.Value == Money.Coins(1.2m)));
|
var spentOutpoint = new OutPoint(targetTx,
|
||||||
|
tx.Outputs.FindIndex(txout => txout.Value == Money.Coins(1.2m)));
|
||||||
await TestUtils.EventuallyAsync(async () =>
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
{
|
{
|
||||||
var store = await s.Server.PayTester.StoreRepository.FindStore(storeId);
|
var store = await s.Server.PayTester.StoreRepository.FindStore(storeId);
|
||||||
@@ -653,7 +663,8 @@ namespace BTCPayServer.Tests
|
|||||||
s.GoToWallet(walletId);
|
s.GoToWallet(walletId);
|
||||||
s.Driver.WaitForAndClick(By.Id("toggleInputSelection"));
|
s.Driver.WaitForAndClick(By.Id("toggleInputSelection"));
|
||||||
s.Driver.WaitForElement(By.Id(spentOutpoint.ToString()));
|
s.Driver.WaitForElement(By.Id(spentOutpoint.ToString()));
|
||||||
Assert.Equal("true", s.Driver.FindElement(By.Name("InputSelection")).GetAttribute("value").ToLowerInvariant());
|
Assert.Equal("true",
|
||||||
|
s.Driver.FindElement(By.Name("InputSelection")).GetAttribute("value").ToLowerInvariant());
|
||||||
var el = s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
|
var el = s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
|
||||||
s.Driver.FindElement(By.Id(spentOutpoint.ToString())).Click();
|
s.Driver.FindElement(By.Id(spentOutpoint.ToString())).Click();
|
||||||
var inputSelectionSelect = s.Driver.FindElement(By.Name("SelectedInputs"));
|
var inputSelectionSelect = s.Driver.FindElement(By.Name("SelectedInputs"));
|
||||||
@@ -723,6 +734,7 @@ namespace BTCPayServer.Tests
|
|||||||
// Fix as needed.
|
// Fix as needed.
|
||||||
Assert.Contains($"value=\"{value}\"", s.Driver.PageSource);
|
Assert.Contains($"value=\"{value}\"", s.Driver.PageSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This one should be checked
|
// This one should be checked
|
||||||
Assert.Contains($"value=\"InvoiceProcessing\" checked", s.Driver.PageSource);
|
Assert.Contains($"value=\"InvoiceProcessing\" checked", s.Driver.PageSource);
|
||||||
Assert.Contains($"value=\"InvoiceCreated\" checked", s.Driver.PageSource);
|
Assert.Contains($"value=\"InvoiceCreated\" checked", s.Driver.PageSource);
|
||||||
@@ -741,7 +753,8 @@ namespace BTCPayServer.Tests
|
|||||||
var headers = request.Request.Headers;
|
var headers = request.Request.Headers;
|
||||||
var actualSig = headers["BTCPay-Sig"].First();
|
var actualSig = headers["BTCPay-Sig"].First();
|
||||||
var bytes = await request.Request.Body.ReadBytesAsync((int)headers.ContentLength.Value);
|
var bytes = await request.Request.Body.ReadBytesAsync((int)headers.ContentLength.Value);
|
||||||
var expectedSig = $"sha256={Encoders.Hex.EncodeData(new HMACSHA256(Encoding.UTF8.GetBytes("HelloWorld")).ComputeHash(bytes))}";
|
var expectedSig =
|
||||||
|
$"sha256={Encoders.Hex.EncodeData(new HMACSHA256(Encoding.UTF8.GetBytes("HelloWorld")).ComputeHash(bytes))}";
|
||||||
Assert.Equal(expectedSig, actualSig);
|
Assert.Equal(expectedSig, actualSig);
|
||||||
request.Response.StatusCode = 200;
|
request.Response.StatusCode = 200;
|
||||||
server.Done();
|
server.Done();
|
||||||
@@ -799,7 +812,8 @@ namespace BTCPayServer.Tests
|
|||||||
foreach (var isHotwallet in new[] { false, true })
|
foreach (var isHotwallet in new[] { false, true })
|
||||||
{
|
{
|
||||||
var (storeName, storeId) = s.CreateNewStore();
|
var (storeName, storeId) = s.CreateNewStore();
|
||||||
s.GenerateWallet(privkeys: isHotwallet, seed: "melody lizard phrase voice unique car opinion merge degree evil swift cargo");
|
s.GenerateWallet(privkeys: isHotwallet,
|
||||||
|
seed: "melody lizard phrase voice unique car opinion merge degree evil swift cargo");
|
||||||
s.GoToWallet(s.WalletId, WalletsNavPages.Settings);
|
s.GoToWallet(s.WalletId, WalletsNavPages.Settings);
|
||||||
if (isHotwallet)
|
if (isHotwallet)
|
||||||
Assert.Contains("View seed", s.Driver.PageSource);
|
Assert.Contains("View seed", s.Driver.PageSource);
|
||||||
@@ -847,7 +861,8 @@ namespace BTCPayServer.Tests
|
|||||||
var sess = await s.Server.ExplorerClient.CreateWebsocketNotificationSessionAsync();
|
var sess = await s.Server.ExplorerClient.CreateWebsocketNotificationSessionAsync();
|
||||||
await sess.ListenAllTrackedSourceAsync();
|
await sess.ListenAllTrackedSourceAsync();
|
||||||
var nextEvent = sess.NextEventAsync();
|
var nextEvent = sess.NextEventAsync();
|
||||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(receiveAddr, Network.RegTest), Money.Parse("0.1"));
|
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(receiveAddr, Network.RegTest),
|
||||||
|
Money.Parse("0.1"));
|
||||||
await nextEvent;
|
await nextEvent;
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
s.Driver.Navigate().Refresh();
|
s.Driver.Navigate().Refresh();
|
||||||
@@ -870,7 +885,8 @@ namespace BTCPayServer.Tests
|
|||||||
var address = invoice.EntityToDTO().Addresses["BTC"];
|
var address = invoice.EntityToDTO().Addresses["BTC"];
|
||||||
|
|
||||||
//wallet should have been imported to bitcoin core wallet in watch only mode.
|
//wallet should have been imported to bitcoin core wallet in watch only mode.
|
||||||
var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
var result =
|
||||||
|
await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||||
Assert.True(result.IsWatchOnly);
|
Assert.True(result.IsWatchOnly);
|
||||||
s.GoToStore(storeId);
|
s.GoToStore(storeId);
|
||||||
var mnemonic = s.GenerateWallet("BTC", "", true, true);
|
var mnemonic = s.GenerateWallet("BTC", "", true, true);
|
||||||
@@ -880,21 +896,25 @@ namespace BTCPayServer.Tests
|
|||||||
invoiceId = s.CreateInvoice(storeName);
|
invoiceId = s.CreateInvoice(storeName);
|
||||||
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||||
address = invoice.EntityToDTO().Addresses["BTC"];
|
address = invoice.EntityToDTO().Addresses["BTC"];
|
||||||
result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
result = await s.Server.ExplorerNode.GetAddressInfoAsync(
|
||||||
|
BitcoinAddress.Create(address, Network.RegTest));
|
||||||
//spendable from bitcoin core wallet!
|
//spendable from bitcoin core wallet!
|
||||||
Assert.False(result.IsWatchOnly);
|
Assert.False(result.IsWatchOnly);
|
||||||
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest), Money.Coins(3.0m));
|
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest),
|
||||||
|
Money.Coins(3.0m));
|
||||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||||
|
|
||||||
s.ClickOnAllSideMenus();
|
s.ClickOnAllSideMenus();
|
||||||
|
|
||||||
// Make sure wallet info is correct
|
// Make sure wallet info is correct
|
||||||
s.Driver.FindElement(By.Id("WalletSettings")).Click();
|
s.Driver.FindElement(By.Id("WalletSettings")).Click();
|
||||||
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(), s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(),
|
||||||
Assert.Contains("m/84'/1'/0'", s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
|
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
||||||
|
Assert.Contains("m/84'/1'/0'",
|
||||||
|
s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
|
||||||
|
|
||||||
// Make sure we can rescan, because we are admin!
|
// Make sure we can rescan, because we are admin!
|
||||||
s.Driver.FindElement(By.Id("WalletRescan")).Click();
|
s.Driver.FindElement(By.Id("WalletRescan")).Click();
|
||||||
@@ -908,7 +928,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
var walletTransactionLink = s.Driver.Url;
|
var walletTransactionLink = s.Driver.Url;
|
||||||
Assert.Contains(tx.ToString(), s.Driver.PageSource);
|
Assert.Contains(tx.ToString(), s.Driver.PageSource);
|
||||||
|
|
||||||
// Send to bob
|
// Send to bob
|
||||||
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
||||||
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
|
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
|
||||||
@@ -946,8 +966,10 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.SwitchTo().Alert().SendKeys(bip21);
|
s.Driver.SwitchTo().Alert().SendKeys(bip21);
|
||||||
s.Driver.SwitchTo().Alert().Accept();
|
s.Driver.SwitchTo().Alert().Accept();
|
||||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Info);
|
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Info);
|
||||||
Assert.Equal(parsedBip21.Amount.ToString(false), s.Driver.FindElement(By.Id("Outputs_0__Amount")).GetAttribute("value"));
|
Assert.Equal(parsedBip21.Amount.ToString(false),
|
||||||
Assert.Equal(parsedBip21.Address.ToString(), s.Driver.FindElement(By.Id("Outputs_0__DestinationAddress")).GetAttribute("value"));
|
s.Driver.FindElement(By.Id("Outputs_0__Amount")).GetAttribute("value"));
|
||||||
|
Assert.Equal(parsedBip21.Address.ToString(),
|
||||||
|
s.Driver.FindElement(By.Id("Outputs_0__DestinationAddress")).GetAttribute("value"));
|
||||||
|
|
||||||
s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings);
|
s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings);
|
||||||
var walletUrl = s.Driver.Url;
|
var walletUrl = s.Driver.Url;
|
||||||
@@ -955,9 +977,11 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click();
|
s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click();
|
||||||
|
|
||||||
// Seed backup page
|
// Seed backup page
|
||||||
var recoveryPhrase = s.Driver.FindElements(By.Id("RecoveryPhrase")).First().GetAttribute("data-mnemonic");
|
var recoveryPhrase = s.Driver.FindElements(By.Id("RecoveryPhrase")).First()
|
||||||
|
.GetAttribute("data-mnemonic");
|
||||||
Assert.Equal(mnemonic.ToString(), recoveryPhrase);
|
Assert.Equal(mnemonic.ToString(), recoveryPhrase);
|
||||||
Assert.Contains("The recovery phrase will also be stored on the server as a hot wallet.", s.Driver.PageSource);
|
Assert.Contains("The recovery phrase will also be stored on the server as a hot wallet.",
|
||||||
|
s.Driver.PageSource);
|
||||||
|
|
||||||
// No confirmation, just a link to return to the wallet
|
// No confirmation, just a link to return to the wallet
|
||||||
Assert.Empty(s.Driver.FindElements(By.Id("confirm")));
|
Assert.Empty(s.Driver.FindElements(By.Id("confirm")));
|
||||||
@@ -974,12 +998,15 @@ namespace BTCPayServer.Tests
|
|||||||
await s.StartAsync();
|
await s.StartAsync();
|
||||||
s.RegisterNewUser(true);
|
s.RegisterNewUser(true);
|
||||||
var (_, storeId) = s.CreateNewStore();
|
var (_, storeId) = s.CreateNewStore();
|
||||||
var mnemonic = s.GenerateWallet("BTC", "click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel");
|
var mnemonic = s.GenerateWallet("BTC",
|
||||||
|
"click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel");
|
||||||
|
|
||||||
// Make sure wallet info is correct
|
// Make sure wallet info is correct
|
||||||
s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings);
|
s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings);
|
||||||
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(), s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(),
|
||||||
Assert.Contains( "m/84'/1'/0'", s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
|
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
|
||||||
|
Assert.Contains("m/84'/1'/0'",
|
||||||
|
s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1001,7 +1028,8 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");;
|
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");
|
||||||
|
;
|
||||||
s.Driver.FindElement(By.Id("Create")).Click();
|
s.Driver.FindElement(By.Id("Create")).Click();
|
||||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||||
|
|
||||||
@@ -1046,7 +1074,7 @@ namespace BTCPayServer.Tests
|
|||||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||||
payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||||
payouts[0].Click();
|
payouts[0].Click();
|
||||||
|
|
||||||
Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout")));
|
Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout")));
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
||||||
@@ -1070,7 +1098,7 @@ namespace BTCPayServer.Tests
|
|||||||
TestUtils.Eventually(() =>
|
TestUtils.Eventually(() =>
|
||||||
{
|
{
|
||||||
s.Driver.Navigate().Refresh();
|
s.Driver.Navigate().Refresh();
|
||||||
|
|
||||||
txs = s.Driver.FindElements(By.ClassName("transaction-link"));
|
txs = s.Driver.FindElements(By.ClassName("transaction-link"));
|
||||||
Assert.Equal(2, txs.Count);
|
Assert.Equal(2, txs.Count);
|
||||||
});
|
});
|
||||||
@@ -1105,7 +1133,7 @@ namespace BTCPayServer.Tests
|
|||||||
s.GenerateWallet("BTC", "", true, true);
|
s.GenerateWallet("BTC", "", true, true);
|
||||||
var newWalletId = new WalletId(newStore.storeId, "BTC");
|
var newWalletId = new WalletId(newStore.storeId, "BTC");
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||||
s.Driver.FindElement(By.Id("Name")).SendKeys("External Test");
|
s.Driver.FindElement(By.Id("Name")).SendKeys("External Test");
|
||||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||||
@@ -1114,7 +1142,7 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
|
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
|
||||||
s.Driver.FindElement(By.Id("Create")).Click();
|
s.Driver.FindElement(By.Id("Create")).Click();
|
||||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||||
|
|
||||||
address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
|
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
|
||||||
@@ -1127,17 +1155,17 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve")).Click();
|
||||||
s.FindAlertMessage();
|
s.FindAlertMessage();
|
||||||
var tx =await s.Server.ExplorerNode.SendToAddressAsync(address, Money.FromUnit(0.001m, MoneyUnit.BTC));
|
var tx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.FromUnit(0.001m, MoneyUnit.BTC));
|
||||||
|
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-view")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-view")).Click();
|
||||||
Assert.Contains(PayoutState.AwaitingPayment.GetStateString(), s.Driver.PageSource);
|
Assert.Contains(PayoutState.AwaitingPayment.GetStateString(), s.Driver.PageSource);
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-selectAllCheckbox")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-selectAllCheckbox")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-actions")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-actions")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).Click();
|
||||||
s.FindAlertMessage();
|
s.FindAlertMessage();
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.InProgress}-view")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.InProgress}-view")).Click();
|
||||||
Assert.Contains(tx.ToString(), s.Driver.PageSource);
|
Assert.Contains(tx.ToString(), s.Driver.PageSource);
|
||||||
|
|
||||||
@@ -1148,7 +1176,7 @@ namespace BTCPayServer.Tests
|
|||||||
s.GenerateWallet("BTC", "", true, true);
|
s.GenerateWallet("BTC", "", true, true);
|
||||||
|
|
||||||
s.GoToStore(newStore.storeId, StoreNavPages.PullPayments);
|
s.GoToStore(newStore.storeId, StoreNavPages.PullPayments);
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||||
|
|
||||||
var paymentMethodOptions = s.Driver.FindElements(By.CssSelector("#PaymentMethods option"));
|
var paymentMethodOptions = s.Driver.FindElements(By.CssSelector("#PaymentMethods option"));
|
||||||
@@ -1168,8 +1196,10 @@ namespace BTCPayServer.Tests
|
|||||||
TimeSpan.FromHours(1), CancellationToken.None)).BOLT11;
|
TimeSpan.FromHours(1), CancellationToken.None)).BOLT11;
|
||||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(bolt);
|
s.Driver.FindElement(By.Id("Destination")).SendKeys(bolt);
|
||||||
s.Driver.FindElement(By.Id("SelectedPaymentMethod")).Click();
|
s.Driver.FindElement(By.Id("SelectedPaymentMethod")).Click();
|
||||||
s.Driver.FindElement(By.CssSelector($"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", PaymentTypes.LightningLike )}]")).Click();
|
s.Driver.FindElement(By.CssSelector(
|
||||||
|
$"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", PaymentTypes.LightningLike)}]"))
|
||||||
|
.Click();
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
|
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
|
||||||
//we do not allow short-life bolts.
|
//we do not allow short-life bolts.
|
||||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error);
|
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error);
|
||||||
@@ -1181,15 +1211,17 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.Id("Destination")).Clear();
|
s.Driver.FindElement(By.Id("Destination")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(bolt);
|
s.Driver.FindElement(By.Id("Destination")).SendKeys(bolt);
|
||||||
s.Driver.FindElement(By.Id("SelectedPaymentMethod")).Click();
|
s.Driver.FindElement(By.Id("SelectedPaymentMethod")).Click();
|
||||||
s.Driver.FindElement(By.CssSelector($"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", PaymentTypes.LightningLike )}]")).Click();
|
s.Driver.FindElement(By.CssSelector(
|
||||||
|
$"#SelectedPaymentMethod option[value={new PaymentMethodId("BTC", PaymentTypes.LightningLike)}]"))
|
||||||
|
.Click();
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
|
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
|
||||||
s.FindAlertMessage();
|
s.FindAlertMessage();
|
||||||
|
|
||||||
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
|
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
|
||||||
|
|
||||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike )}-view")).Click();
|
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-view")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-view")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
||||||
@@ -1199,20 +1231,24 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
s.Driver.FindElement(By.CssSelector("#pay-invoices-form")).Submit();
|
s.Driver.FindElement(By.CssSelector("#pay-invoices-form")).Submit();
|
||||||
//lightning config in tests is very unstable so we can just go ahead and handle it as both
|
//lightning config in tests is very unstable so we can just go ahead and handle it as both
|
||||||
s.FindAlertMessage(new []{StatusMessageModel.StatusSeverity.Error, StatusMessageModel.StatusSeverity.Success});
|
s.FindAlertMessage(new[]
|
||||||
|
{
|
||||||
|
StatusMessageModel.StatusSeverity.Error, StatusMessageModel.StatusSeverity.Success
|
||||||
|
});
|
||||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike )}-view")).Click();
|
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
||||||
|
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
||||||
if (!s.Driver.PageSource.Contains(bolt))
|
if (!s.Driver.PageSource.Contains(bolt))
|
||||||
{
|
{
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-view")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-view")).Click();
|
||||||
Assert.Contains(bolt, s.Driver.PageSource);
|
Assert.Contains(bolt, s.Driver.PageSource);
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-selectAllCheckbox")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-selectAllCheckbox")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-actions")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-actions")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike )}-view")).Click();
|
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.Completed}-view")).Click();
|
||||||
Assert.Contains(bolt, s.Driver.PageSource);
|
Assert.Contains(bolt, s.Driver.PageSource);
|
||||||
@@ -1295,14 +1331,16 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||||
var lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
var lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
|
||||||
var parsed = LNURL.LNURL.Parse(lnurl, out var tag);
|
var parsed = LNURL.LNURL.Parse(lnurl, out var tag);
|
||||||
var fetchedReuqest = Assert.IsType<LNURL.LNURLPayRequest>(await LNURL.LNURL.FetchInformation(parsed, new HttpClient()));
|
var fetchedReuqest =
|
||||||
|
Assert.IsType<LNURL.LNURLPayRequest>(await LNURL.LNURL.FetchInformation(parsed, new HttpClient()));
|
||||||
Assert.Equal(1m, fetchedReuqest.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
Assert.Equal(1m, fetchedReuqest.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||||
Assert.NotEqual(1m, fetchedReuqest.MaxSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
Assert.NotEqual(1m, fetchedReuqest.MaxSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||||
var lnurlResponse = await fetchedReuqest.SendRequest(new LightMoney(0.000001m, LightMoneyUnit.BTC),
|
var lnurlResponse = await fetchedReuqest.SendRequest(new LightMoney(0.000001m, LightMoneyUnit.BTC),
|
||||||
network, new HttpClient());
|
network, new HttpClient());
|
||||||
|
|
||||||
Assert.Equal(new LightMoney(0.000001m, LightMoneyUnit.BTC), lnurlResponse.GetPaymentRequest(network).MinimumAmount);
|
Assert.Equal(new LightMoney(0.000001m, LightMoneyUnit.BTC),
|
||||||
|
lnurlResponse.GetPaymentRequest(network).MinimumAmount);
|
||||||
|
|
||||||
var lnurlResponse2 = await fetchedReuqest.SendRequest(new LightMoney(0.000002m, LightMoneyUnit.BTC),
|
var lnurlResponse2 = await fetchedReuqest.SendRequest(new LightMoney(0.000002m, LightMoneyUnit.BTC),
|
||||||
network, new HttpClient());
|
network, new HttpClient());
|
||||||
Assert.Equal(new LightMoney(0.000002m, LightMoneyUnit.BTC), lnurlResponse2.GetPaymentRequest(network).MinimumAmount);
|
Assert.Equal(new LightMoney(0.000002m, LightMoneyUnit.BTC), lnurlResponse2.GetPaymentRequest(network).MinimumAmount);
|
||||||
@@ -1344,16 +1382,18 @@ namespace BTCPayServer.Tests
|
|||||||
await fetchedReuqest.SendRequest(new LightMoney(0.00000005m, LightMoneyUnit.BTC),
|
await fetchedReuqest.SendRequest(new LightMoney(0.00000005m, LightMoneyUnit.BTC),
|
||||||
network, new HttpClient());
|
network, new HttpClient());
|
||||||
});
|
});
|
||||||
|
|
||||||
lnurlResponse = await fetchedReuqest.SendRequest(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
|
lnurlResponse = await fetchedReuqest.SendRequest(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
|
||||||
network, new HttpClient());
|
network, new HttpClient());
|
||||||
lnurlResponse2 = await fetchedReuqest.SendRequest(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
|
lnurlResponse2 = await fetchedReuqest.SendRequest(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
|
||||||
network, new HttpClient());
|
network, new HttpClient());
|
||||||
|
//invoice amounts do no change so the paymnet request is not regenerated
|
||||||
// Invoice amounts do no change so the payment request is not regenerated
|
Assert.Equal(lnurlResponse.Pr, lnurlResponse2.Pr);
|
||||||
Assert.Equal(lnurlResponse.Pr,lnurlResponse2.Pr);
|
|
||||||
await s.Server.CustomerLightningD.Pay(lnurlResponse.Pr);
|
await s.Server.CustomerLightningD.Pay(lnurlResponse.Pr);
|
||||||
Assert.Equal(new LightMoney(0.0000001m, LightMoneyUnit.BTC), lnurlResponse2.GetPaymentRequest(network).MinimumAmount);
|
Assert.Equal(new LightMoney(0.0000001m, LightMoneyUnit.BTC),
|
||||||
|
lnurlResponse2.GetPaymentRequest(network).MinimumAmount);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
s.GoToStore(s.StoreId);
|
s.GoToStore(s.StoreId);
|
||||||
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
||||||
@@ -1372,11 +1412,11 @@ namespace BTCPayServer.Tests
|
|||||||
i = s.CreateInvoice(store.storeName, null, cryptoCode);
|
i = s.CreateInvoice(store.storeName, null, cryptoCode);
|
||||||
s.GoToInvoiceCheckout(i);
|
s.GoToInvoiceCheckout(i);
|
||||||
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
|
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
|
||||||
|
|
||||||
s.GoToStore(s.StoreId);
|
s.GoToStore(s.StoreId);
|
||||||
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
||||||
s.Driver.SetCheckbox(By.Id("LNURLBech32Mode"), false);
|
s.Driver.SetCheckbox(By.Id("LNURLBech32Mode"), false);
|
||||||
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), true);
|
s.Driver.SetCheckbox(By.Id("LNURLStandardInvoiceEnabled"), false);
|
||||||
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
|
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
|
||||||
s.Driver.FindElement(By.Id("save")).Click();
|
s.Driver.FindElement(By.Id("save")).Click();
|
||||||
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||||
@@ -1384,7 +1424,7 @@ namespace BTCPayServer.Tests
|
|||||||
// Ensure the toggles are set correctly
|
// Ensure the toggles are set correctly
|
||||||
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
s.Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
||||||
Assert.True(s.Driver.FindElement(By.Id("DisableBolt11PaymentMethod")).Selected);
|
Assert.True(s.Driver.FindElement(By.Id("DisableBolt11PaymentMethod")).Selected);
|
||||||
Assert.True(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
|
Assert.False(s.Driver.FindElement(By.Id("LNURLStandardInvoiceEnabled")).Selected);
|
||||||
Assert.False(s.Driver.FindElement(By.Id("LNURLBech32Mode")).Selected);
|
Assert.False(s.Driver.FindElement(By.Id("LNURLBech32Mode")).Selected);
|
||||||
s.CreateInvoice(store.storeName, 0.0000001m, cryptoCode,"",null, expectedSeverity: StatusMessageModel.StatusSeverity.Error);
|
s.CreateInvoice(store.storeName, 0.0000001m, cryptoCode,"",null, expectedSeverity: StatusMessageModel.StatusSeverity.Error);
|
||||||
|
|
||||||
@@ -1416,11 +1456,12 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Equal(new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike),PaymentMethodId.Parse(Assert.Single(s.Driver.FindElement(By.Id("PaymentMethods")).FindElements(By.TagName("option"))).GetAttribute("value")));
|
Assert.Equal(new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike),PaymentMethodId.Parse(Assert.Single(s.Driver.FindElement(By.Id("PaymentMethods")).FindElements(By.TagName("option"))).GetAttribute("value")));
|
||||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.0000001");;
|
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.0000001");
|
||||||
|
;
|
||||||
s.Driver.FindElement(By.Id("Create")).Click();
|
s.Driver.FindElement(By.Id("Create")).Click();
|
||||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(lnurl);
|
s.Driver.FindElement(By.Id("Destination")).SendKeys(lnurl);
|
||||||
|
|
||||||
var pullPaymentId = s.Driver.Url.Split('/').Last();
|
var pullPaymentId = s.Driver.Url.Split('/').Last();
|
||||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("0.0000001" + Keys.Enter);
|
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("0.0000001" + Keys.Enter);
|
||||||
@@ -1429,16 +1470,16 @@ namespace BTCPayServer.Tests
|
|||||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||||
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||||
payouts[0].Click();
|
payouts[0].Click();
|
||||||
s.Driver.FindElement(By.Id("BTC_LightningLike-view")).Click();
|
s.Driver.FindElement(By.Id("BTC_LightningLike-view")).Click();
|
||||||
Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout")));
|
Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout")));
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
||||||
|
|
||||||
Assert.Contains(lnurl, s.Driver.PageSource);
|
Assert.Contains(lnurl, s.Driver.PageSource);
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("pay-invoices-form")).Submit();
|
s.Driver.FindElement(By.Id("pay-invoices-form")).Submit();
|
||||||
|
|
||||||
await TestUtils.EventuallyAsync(async () =>
|
await TestUtils.EventuallyAsync(async () =>
|
||||||
{
|
{
|
||||||
var inv = await s.Server.PayTester.InvoiceRepository.GetInvoice(invForPP);
|
var inv = await s.Server.PayTester.InvoiceRepository.GetInvoice(invForPP);
|
||||||
@@ -1450,6 +1491,87 @@ namespace BTCPayServer.Tests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Selenium", "Selenium")]
|
||||||
|
[Trait("Lightning", "Lightning")]
|
||||||
|
public async Task CanUseLNAddress()
|
||||||
|
{
|
||||||
|
using var s = SeleniumTester.Create();
|
||||||
|
s.Server.ActivateLightning();
|
||||||
|
await s.StartAsync();
|
||||||
|
|
||||||
|
await s.Server.EnsureChannelsSetup();
|
||||||
|
s.RegisterNewUser(true);
|
||||||
|
//ln address tests
|
||||||
|
var store = s.CreateNewStore();
|
||||||
|
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
|
||||||
|
//ensure ln address is not available as lnurl is not configured
|
||||||
|
s.Driver.FindElement(By.Id("lightning-address-option"))
|
||||||
|
.FindElement(By.ClassName("btcpay-status--disabled"));
|
||||||
|
|
||||||
|
s.GoToStore(s.StoreId, StoreNavPages.PaymentMethods);
|
||||||
|
s.AddLightningNode("BTC", LightningConnectionType.LndREST, false);
|
||||||
|
|
||||||
|
s.Driver.FindElement(By.Id($"Modify-LightningBTC")).Click();
|
||||||
|
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
|
||||||
|
s.Driver.WaitForAndClick(By.Id("save"));
|
||||||
|
Assert.Contains($"BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
|
||||||
|
|
||||||
|
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
|
||||||
|
s.Driver.FindElement(By.Id("lightning-address-option"))
|
||||||
|
.FindElement(By.Id("lightning-address-setup-link")).Click();
|
||||||
|
|
||||||
|
s.Driver.ToggleCollapse("AddAddress");
|
||||||
|
var lnaddress1 = Guid.NewGuid().ToString();
|
||||||
|
s.Driver.FindElement(By.Id("Add_Username")).SendKeys(lnaddress1);
|
||||||
|
s.Driver.FindElement(By.CssSelector("button[value='add']")).Click();
|
||||||
|
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||||
|
|
||||||
|
s.Driver.ToggleCollapse("AddAddress");
|
||||||
|
s.Driver.FindElement(By.Id("Add_Username")).SendKeys(lnaddress1);
|
||||||
|
s.Driver.FindElement(By.CssSelector("button[value='add']")).Click();
|
||||||
|
s.Driver.FindElement(By.ClassName("text-danger"));
|
||||||
|
|
||||||
|
s.Driver.FindElement(By.Id("Add_Username")).Clear();
|
||||||
|
var lnaddress2 = "EUR" + Guid.NewGuid().ToString();
|
||||||
|
s.Driver.FindElement(By.Id("Add_Username")).SendKeys(lnaddress2);
|
||||||
|
|
||||||
|
s.Driver.ToggleCollapse("AdvancedSettings");
|
||||||
|
s.Driver.FindElement(By.Id("Add_CurrencyCode")).SendKeys("EUR");
|
||||||
|
s.Driver.FindElement(By.Id("Add_Min")).SendKeys("2");
|
||||||
|
s.Driver.FindElement(By.Id("Add_Max")).SendKeys("10");
|
||||||
|
s.Driver.FindElement(By.CssSelector("button[value='add']")).Click();
|
||||||
|
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||||
|
|
||||||
|
var addresses = s.Driver.FindElements(By.ClassName("lightning-address-value"));
|
||||||
|
Assert.Equal(2, addresses.Count);
|
||||||
|
|
||||||
|
foreach (IWebElement webElement in addresses)
|
||||||
|
{
|
||||||
|
var value = webElement.GetAttribute("value");
|
||||||
|
//cannot test this directly as https is not supported on our e2e tests
|
||||||
|
// var request = await LNURL.LNURL.FetchPayRequestViaInternetIdentifier(value, new HttpClient());
|
||||||
|
|
||||||
|
var lnurl = new Uri(LNURL.LNURL.ExtractUriFromInternetIdentifier(value).ToString()
|
||||||
|
.Replace("https", "http"));
|
||||||
|
var request =(LNURL.LNURLPayRequest) await LNURL.LNURL.FetchInformation(lnurl, new HttpClient());
|
||||||
|
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case { } v when v.StartsWith(lnaddress2):
|
||||||
|
Assert.Equal(2, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||||
|
Assert.Equal(10, request.MaxSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case { } v when v.StartsWith(lnaddress1):
|
||||||
|
Assert.Equal(1, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||||
|
Assert.Equal(6.12m, request.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void CanBrowseContent(SeleniumTester s)
|
private static void CanBrowseContent(SeleniumTester s)
|
||||||
{
|
{
|
||||||
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
|
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
|
||||||
|
|||||||
@@ -332,6 +332,8 @@ namespace BTCPayServer.Hosting
|
|||||||
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<LightningLikePaymentHandler>());
|
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<LightningLikePaymentHandler>());
|
||||||
services.AddSingleton<LNURLPayPaymentHandler>();
|
services.AddSingleton<LNURLPayPaymentHandler>();
|
||||||
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<LNURLPayPaymentHandler>());
|
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<LNURLPayPaymentHandler>());
|
||||||
|
services.AddSingleton<IUIExtension>(new UIExtension("LNURL/LightningAddressOption",
|
||||||
|
"store-integrations-list"));
|
||||||
services.AddSingleton<IHostedService, LightningListener>();
|
services.AddSingleton<IHostedService, LightningListener>();
|
||||||
|
|
||||||
services.AddSingleton<PaymentMethodHandlerDictionary>();
|
services.AddSingleton<PaymentMethodHandlerDictionary>();
|
||||||
|
|||||||
@@ -21,11 +21,13 @@ using BTCPayServer.Payments.Lightning;
|
|||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using LNURL;
|
using LNURL;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.Crypto;
|
using NBitcoin.Crypto;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -103,7 +105,7 @@ namespace BTCPayServer
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewPointOfSaleViewModel.Item[] items = { };
|
ViewPointOfSaleViewModel.Item[] items = null;
|
||||||
string currencyCode = null;
|
string currencyCode = null;
|
||||||
switch (app.AppType)
|
switch (app.AppType)
|
||||||
{
|
{
|
||||||
@@ -133,6 +135,62 @@ namespace BTCPayServer
|
|||||||
() => (null, new List<string> { AppService.GetAppInternalTag(appId) }, item.Price.Value, true));
|
() => (null, new List<string> { AppService.GetAppInternalTag(appId) }, item.Price.Value, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class EditLightningAddressVM
|
||||||
|
{
|
||||||
|
public class EditLightningAddressItem : LightningAddressSettings.LightningAddressItem
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[RegularExpression("[a-zA-Z0-9-_]+")]
|
||||||
|
public string Username { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public EditLightningAddressItem Add { get; set; }
|
||||||
|
public List<EditLightningAddressItem> Items { get; set; } = new List<EditLightningAddressItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LightningAddressSettings
|
||||||
|
{
|
||||||
|
public class LightningAddressItem
|
||||||
|
{
|
||||||
|
public string StoreId { get; set; }
|
||||||
|
[Display(Name = "Invoice currency")]
|
||||||
|
public string CurrencyCode { get; set; }
|
||||||
|
public string CryptoCode { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Min sats")]
|
||||||
|
[Range(1, double.PositiveInfinity)]
|
||||||
|
public decimal? Min { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Max sats")]
|
||||||
|
[Range(1, double.PositiveInfinity)]
|
||||||
|
public decimal? Max { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConcurrentDictionary<string, LightningAddressItem> Items { get; set; } =
|
||||||
|
new ConcurrentDictionary<string, LightningAddressItem>();
|
||||||
|
|
||||||
|
public ConcurrentDictionary<string, string[]> StoreToItemMap { get; set; } =
|
||||||
|
new ConcurrentDictionary<string, string[]>();
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("~/.well-known/lnurlp/{username}")]
|
||||||
|
public async Task<IActionResult> ResolveLightningAddress(string username)
|
||||||
|
{
|
||||||
|
var lightningAddressSettings = await _settingsRepository.GetSettingAsync<LightningAddressSettings>() ??
|
||||||
|
new LightningAddressSettings();
|
||||||
|
if (!lightningAddressSettings.Items.TryGetValue(username.ToLowerInvariant(), out var item))
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await GetLNURL(item.CryptoCode, item.StoreId, item.CurrencyCode, item.Min, item.Max,
|
||||||
|
() => (username, null, null, true));
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("pay")]
|
[HttpGet("pay")]
|
||||||
public async Task<IActionResult> GetLNURL(string cryptoCode, string storeId, string currencyCode = null,
|
public async Task<IActionResult> GetLNURL(string cryptoCode, string storeId, string currencyCode = null,
|
||||||
@@ -140,7 +198,6 @@ namespace BTCPayServer
|
|||||||
Func<(string username, List<string> additionalTags, decimal? invoiceAmount, bool? anyoneCanInvoice)>
|
Func<(string username, List<string> additionalTags, decimal? invoiceAmount, bool? anyoneCanInvoice)>
|
||||||
internalDetails = null)
|
internalDetails = null)
|
||||||
{
|
{
|
||||||
currencyCode ??= cryptoCode;
|
|
||||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||||
if (network is null || !network.SupportLightning)
|
if (network is null || !network.SupportLightning)
|
||||||
{
|
{
|
||||||
@@ -153,6 +210,7 @@ namespace BTCPayServer
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currencyCode ??= store.GetStoreBlob().DefaultCurrency ?? cryptoCode;
|
||||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||||
var lnpmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
var lnpmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||||
var methods = store.GetSupportedPaymentMethods(_btcPayNetworkProvider);
|
var methods = store.GetSupportedPaymentMethods(_btcPayNetworkProvider);
|
||||||
@@ -178,6 +236,7 @@ namespace BTCPayServer
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lnAddress = username is null ? null : $"{username}@{Request.Host.ToString()}";
|
||||||
List<string[]> lnurlMetadata = new List<string[]>();
|
List<string[]> lnurlMetadata = new List<string[]>();
|
||||||
|
|
||||||
var i = await _invoiceController.CreateInvoiceCoreRaw(
|
var i = await _invoiceController.CreateInvoiceCoreRaw(
|
||||||
@@ -200,7 +259,21 @@ namespace BTCPayServer
|
|||||||
max = min;
|
max = min;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(username))
|
||||||
|
{
|
||||||
|
var pm = i.GetPaymentMethod(pmi);
|
||||||
|
var paymentMethodDetails = (LNURLPayPaymentMethodDetails)pm.GetPaymentMethodDetails();
|
||||||
|
paymentMethodDetails.ConsumedLightningAddress = lnAddress;
|
||||||
|
pm.SetPaymentMethodDetails(paymentMethodDetails);
|
||||||
|
await _invoiceRepository.UpdateInvoicePaymentMethod(i.Id, pm);
|
||||||
|
}
|
||||||
|
|
||||||
lnurlMetadata.Add(new[] { "text/plain", i.Id });
|
lnurlMetadata.Add(new[] { "text/plain", i.Id });
|
||||||
|
if (!string.IsNullOrEmpty(username))
|
||||||
|
{
|
||||||
|
lnurlMetadata.Add(new[] { "text/identifier", lnAddress });
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(new LNURLPayRequest
|
return Ok(new LNURLPayRequest
|
||||||
{
|
{
|
||||||
Tag = "payRequest",
|
Tag = "payRequest",
|
||||||
@@ -258,6 +331,10 @@ namespace BTCPayServer
|
|||||||
List<string[]> lnurlMetadata = new List<string[]>();
|
List<string[]> lnurlMetadata = new List<string[]>();
|
||||||
|
|
||||||
lnurlMetadata.Add(new[] { "text/plain", i.Id });
|
lnurlMetadata.Add(new[] { "text/plain", i.Id });
|
||||||
|
if (!string.IsNullOrEmpty(paymentMethodDetails.ConsumedLightningAddress))
|
||||||
|
{
|
||||||
|
lnurlMetadata.Add(new[] { "text/identifier", paymentMethodDetails.ConsumedLightningAddress });
|
||||||
|
}
|
||||||
|
|
||||||
var metadata = JsonConvert.SerializeObject(lnurlMetadata);
|
var metadata = JsonConvert.SerializeObject(lnurlMetadata);
|
||||||
if (amount.HasValue && (amount < min || amount > max))
|
if (amount.HasValue && (amount < min || amount > max))
|
||||||
@@ -303,7 +380,7 @@ namespace BTCPayServer
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
return BadRequest(new LNUrlStatusResponse
|
return BadRequest(new LNUrlStatusResponse
|
||||||
{
|
{
|
||||||
@@ -323,6 +400,7 @@ namespace BTCPayServer
|
|||||||
lightningPaymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
lightningPaymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
||||||
await _invoiceRepository.UpdateInvoicePaymentMethod(invoiceId, lightningPaymentMethod);
|
await _invoiceRepository.UpdateInvoicePaymentMethod(invoiceId, lightningPaymentMethod);
|
||||||
|
|
||||||
|
|
||||||
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(invoiceId,
|
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(invoiceId,
|
||||||
paymentMethodDetails, pmi));
|
paymentMethodDetails, pmi));
|
||||||
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
||||||
@@ -365,5 +443,124 @@ namespace BTCPayServer
|
|||||||
Status = "ERROR", Reason = "Invoice not in a valid payable state"
|
Status = "ERROR", Reason = "Invoice not in a valid payable state"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
[HttpGet("~/stores/{storeId}/integrations/lightning-address")]
|
||||||
|
public async Task<IActionResult> EditLightningAddress(string storeId)
|
||||||
|
{
|
||||||
|
if (ControllerContext.HttpContext.GetStoreData().GetEnabledPaymentIds(_btcPayNetworkProvider).All(id => id.PaymentType != LNURLPayPaymentType.Instance))
|
||||||
|
{
|
||||||
|
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||||
|
{
|
||||||
|
Message = "LNURL is required for lightning addresses but has not yet been enabled.",
|
||||||
|
Severity = StatusMessageModel.StatusSeverity.Error
|
||||||
|
});
|
||||||
|
return RedirectToAction("PaymentMethods", "Stores", new { storeId });
|
||||||
|
}
|
||||||
|
var lightningAddressSettings = await _settingsRepository.GetSettingAsync<LightningAddressSettings>() ??
|
||||||
|
new LightningAddressSettings();
|
||||||
|
if (lightningAddressSettings.StoreToItemMap.TryGetValue(storeId, out var addresses))
|
||||||
|
{
|
||||||
|
return View(new EditLightningAddressVM
|
||||||
|
{
|
||||||
|
Items = addresses.Select(s => new EditLightningAddressVM.EditLightningAddressItem
|
||||||
|
{
|
||||||
|
Max = lightningAddressSettings.Items[s].Max,
|
||||||
|
Min = lightningAddressSettings.Items[s].Min,
|
||||||
|
CurrencyCode = lightningAddressSettings.Items[s].CurrencyCode,
|
||||||
|
CryptoCode = lightningAddressSettings.Items[s].CryptoCode,
|
||||||
|
StoreId = lightningAddressSettings.Items[s].StoreId,
|
||||||
|
Username = s,
|
||||||
|
}).ToList()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return View(new EditLightningAddressVM
|
||||||
|
{
|
||||||
|
Items = new List<EditLightningAddressVM.EditLightningAddressItem>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
|
[HttpPost("~/stores/{storeId}/integrations/lightning-address")]
|
||||||
|
public async Task<IActionResult> EditLightningAddress(string storeId, [FromForm] EditLightningAddressVM vm,
|
||||||
|
string command, [FromServices] CurrencyNameTable currencyNameTable)
|
||||||
|
{
|
||||||
|
if (command == "add")
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(vm.Add.CurrencyCode) && currencyNameTable.GetCurrencyData(vm.Add.CurrencyCode, false) is null)
|
||||||
|
{
|
||||||
|
vm.AddModelError(addressVm => addressVm.Add.CurrencyCode, "Currency is invalid", this);
|
||||||
|
}
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View(vm);
|
||||||
|
}
|
||||||
|
var lightningAddressSettings = await _settingsRepository.GetSettingAsync<LightningAddressSettings>() ??
|
||||||
|
new LightningAddressSettings();
|
||||||
|
if (lightningAddressSettings.Items.ContainsKey(vm.Add.Username.ToLowerInvariant()))
|
||||||
|
{
|
||||||
|
vm.AddModelError(addressVm => addressVm.Add.Username, "Username is already taken", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View(vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lightningAddressSettings.StoreToItemMap.TryGetValue(storeId, out var ids))
|
||||||
|
{
|
||||||
|
ids = ids.Concat(new[] { vm.Add.Username.ToLowerInvariant() }).ToArray();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ids = new[] { vm.Add.Username.ToLowerInvariant() };
|
||||||
|
}
|
||||||
|
|
||||||
|
lightningAddressSettings.StoreToItemMap.AddOrReplace(storeId, ids);
|
||||||
|
vm.Add.StoreId = storeId;
|
||||||
|
vm.Add.CryptoCode = ControllerContext.HttpContext.GetStoreData()
|
||||||
|
.GetEnabledPaymentIds(_btcPayNetworkProvider)
|
||||||
|
.OrderBy(id => id.CryptoCode == "BTC")
|
||||||
|
.First()
|
||||||
|
.CryptoCode;
|
||||||
|
lightningAddressSettings.Items.TryAdd(vm.Add.Username.ToLowerInvariant(), vm.Add);
|
||||||
|
await _settingsRepository.UpdateSetting(lightningAddressSettings);
|
||||||
|
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||||
|
{
|
||||||
|
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||||
|
Message = "Lightning address added successfully."
|
||||||
|
});
|
||||||
|
|
||||||
|
return RedirectToAction("EditLightningAddress");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.StartsWith("remove", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
var lightningAddressSettings = await _settingsRepository.GetSettingAsync<LightningAddressSettings>() ??
|
||||||
|
new LightningAddressSettings();
|
||||||
|
var index = int.Parse(
|
||||||
|
command.Substring(command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1),
|
||||||
|
CultureInfo.InvariantCulture);
|
||||||
|
if (lightningAddressSettings.StoreToItemMap.TryGetValue(storeId, out var addresses))
|
||||||
|
{
|
||||||
|
var addressToRemove = addresses[index];
|
||||||
|
addresses = addresses.Where(s => s != addressToRemove).ToArray();
|
||||||
|
lightningAddressSettings.StoreToItemMap.AddOrReplace(storeId, addresses);
|
||||||
|
lightningAddressSettings.Items.TryRemove(addressToRemove, out _);
|
||||||
|
await _settingsRepository.UpdateSetting(lightningAddressSettings);
|
||||||
|
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||||
|
{
|
||||||
|
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||||
|
Message = $"Lightning address {addressToRemove} removed successfully."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction("EditLightningAddress");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace BTCPayServer.Payments
|
|||||||
public bool Bech32Mode { get; set; }
|
public bool Bech32Mode { get; set; }
|
||||||
|
|
||||||
public string ProvidedComment { get; set; }
|
public string ProvidedComment { get; set; }
|
||||||
|
public string ConsumedLightningAddress { get; set; }
|
||||||
|
|
||||||
public override PaymentType GetPaymentType()
|
public override PaymentType GetPaymentType()
|
||||||
{
|
{
|
||||||
@@ -23,7 +24,7 @@ namespace BTCPayServer.Payments
|
|||||||
|
|
||||||
public override string GetAdditionalDataPartialName()
|
public override string GetAdditionalDataPartialName()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(ProvidedComment))
|
if (string.IsNullOrEmpty(ProvidedComment) && string.IsNullOrEmpty(ConsumedLightningAddress))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
144
BTCPayServer/Views/LNURL/EditLightningAddress.cshtml
Normal file
144
BTCPayServer/Views/LNURL/EditLightningAddress.cshtml
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
@using BTCPayServer.Views.Stores
|
||||||
|
@using BTCPayServer.Abstractions.Extensions
|
||||||
|
@model LNURLController.EditLightningAddressVM
|
||||||
|
@{
|
||||||
|
Layout = "../Shared/_NavLayout.cshtml";
|
||||||
|
ViewData["NavPartialName"] = "../Stores/_Nav";
|
||||||
|
ViewData.SetActivePageAndTitle(StoreNavPages.Integrations, "Lightning Address Setup", Context.GetStoreData().StoreName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@section PageHeadContent {
|
||||||
|
<style>
|
||||||
|
.settings-holder span:not(:last-child):after{
|
||||||
|
content: " / ";
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
}
|
||||||
|
|
||||||
|
@section PageFootContent {
|
||||||
|
<script>
|
||||||
|
delegate('click', '.remove', event => {
|
||||||
|
event.preventDefault()
|
||||||
|
const { name, value } = event.target
|
||||||
|
const confirmButton = document.getElementById('ConfirmContinue')
|
||||||
|
confirmButton.setAttribute('name', name)
|
||||||
|
confirmButton.setAttribute('value', value)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="d-sm-flex align-items-center justify-content-between mb-2">
|
||||||
|
<h2 class="mb-3 mb-sm-0">@ViewData["PageTitle"]</h2>
|
||||||
|
<a data-bs-toggle="collapse" data-bs-target="#AddAddress" class="btn btn-primary" role="button">
|
||||||
|
<span class="fa fa-plus"></span>
|
||||||
|
Add Address
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="EditLightningAddress" method="post">
|
||||||
|
@{
|
||||||
|
var showAddForm = !ViewContext.ViewData.ModelState.IsValid || !string.IsNullOrEmpty(Model.Add?.Username) || Model.Add?.Max != null || Model.Add?.Min != null || !string.IsNullOrEmpty(Model.Add?.CurrencyCode);
|
||||||
|
var showAdvancedOptions = !string.IsNullOrEmpty(Model.Add?.CurrencyCode) || Model.Add?.Min != null || Model.Add?.Max != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="row collapse @(showAddForm ? "show": "")" id="AddAddress">
|
||||||
|
<div class="form-group pt-3">
|
||||||
|
<label asp-for="Add.Username" class="form-label"></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input asp-for="Add.Username" class="form-control"/>
|
||||||
|
<span class="input-group-text" >@@@Context.Request.Host.ToUriComponent()@Context.Request.PathBase</span>
|
||||||
|
</div>
|
||||||
|
<span asp-validation-for="Add.Username" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<a class="mb-3" role="button" data-bs-toggle="collapse" data-bs-target="#AdvancedSettings">Advanced settings</a>
|
||||||
|
<div id="AdvancedSettings" class="collapse @(showAdvancedOptions ? "show" : "")">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-sm-auto">
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="Add.CurrencyCode" class="form-label"></label>
|
||||||
|
<input asp-for="Add.CurrencyCode" class="form-control" style="max-width:16ch;"/>
|
||||||
|
<span asp-validation-for="Add.CurrencyCode" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-auto">
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="Add.Min" class="form-label"></label>
|
||||||
|
<input asp-for="Add.Min" class="form-control" type="number" min="1" style="max-width:16ch;"/>
|
||||||
|
<span asp-validation-for="Add.Min" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-auto">
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="Add.Max" class="form-label"></label>
|
||||||
|
<input asp-for="Add.Max" class="form-control" type="number" min="1" max="@int.MaxValue" style="max-width:16ch;"/>
|
||||||
|
<span asp-validation-for="Add.Max" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" name="command" value="add" class="btn btn-primary">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Model.Items.Any())
|
||||||
|
{
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Address</th>
|
||||||
|
<th>Settings</th>
|
||||||
|
<th class="text-end">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (var index = 0; index < Model.Items.Count; index++)
|
||||||
|
{
|
||||||
|
<input asp-for="Items[index].CurrencyCode" type="hidden"/>
|
||||||
|
<input asp-for="Items[index].Min" type="hidden"/>
|
||||||
|
<input asp-for="Items[index].Max" type="hidden"/>
|
||||||
|
<input asp-for="Items[index].Username" type="hidden"/>
|
||||||
|
var address = $"{Model.Items[index].Username}@{Context.Request.Host.ToUriComponent()}{Context.Request.PathBase}";
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="input-group" data-clipboard="@address">
|
||||||
|
<input type="text" class="form-control copy-cursor lightning-address-value" readonly="readonly" value="@address"/>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-clipboard-confirm>Copy</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td class="settings-holder align-middle">
|
||||||
|
@if (Model.Items[index].Min.HasValue)
|
||||||
|
{
|
||||||
|
<span>@Safe.Raw($"{Model.Items[index].Min} min sats")</span>
|
||||||
|
}
|
||||||
|
@if (Model.Items[index].Max.HasValue)
|
||||||
|
{
|
||||||
|
<span> @Safe.Raw($"{Model.Items[index].Max} max sats")</span>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.Items[index].CurrencyCode))
|
||||||
|
{
|
||||||
|
<span> @Safe.Raw($"tracked in {Model.Items[index].CurrencyCode}")</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<button type="submit" title="Remove" name="command" value="@($"remove:{index}")"
|
||||||
|
class="btn btn-link px-0 remove" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The Lightning Address <strong>@address</strong> will be removed." data-confirm-input="REMOVE">
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p class="text-secondary mt-3">
|
||||||
|
There are no Lightning Addresses yet.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<partial name="_Confirm" model="@(new ConfirmModel("Remove Lightning Address", "This Lightning Address will be removed.", "Remove"))" />
|
||||||
@@ -10,3 +10,13 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.ConsumedLightningAddress))
|
||||||
|
{
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="100% bg-tile">
|
||||||
|
|
||||||
|
Lightning address used: @Model.ConsumedLightningAddress
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
@using BTCPayServer.Payments.Lightning
|
||||||
|
@inject BTCPayNetworkProvider BTCPayNetworkProvider
|
||||||
|
@{
|
||||||
|
var store = Context.GetStoreData();
|
||||||
|
var possible = store.GetSupportedPaymentMethods(BTCPayNetworkProvider).OfType<LNURLPaySupportedPaymentMethod>().Any(type => type.CryptoCode == "BTC");
|
||||||
|
}
|
||||||
|
<li class="list-group-item bg-tile" id="lightning-address-option">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<span class="d-flex flex-wrap flex-fill flex-column flex-sm-row">
|
||||||
|
<strong class="me-3">
|
||||||
|
Lightning Address
|
||||||
|
<a href="https://lightningaddress.com/" target="_blank" rel="noreferrer noopener">
|
||||||
|
<span class="fa fa-question-circle-o" title="More information..."></span>
|
||||||
|
</a>
|
||||||
|
</strong>
|
||||||
|
</span>
|
||||||
|
<span class="d-flex align-items-center fw-semibold">
|
||||||
|
@if (possible)
|
||||||
|
{
|
||||||
|
<a id="lightning-address-setup-link" class="btn btn-primary btn-sm ms-4 px-3 py-1 fw-semibold" asp-controller="LNURL" asp-action="EditLightningAddress" asp-route-storeId="@Context.GetRouteValue("storeId")">
|
||||||
|
Setup
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="d-flex align-items-center text-danger">
|
||||||
|
<span class="me-2 btcpay-status btcpay-status--disabled"></span>
|
||||||
|
You need LNURL configured first.
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
Reference in New Issue
Block a user