Remove redundant payment methods from store settings (#3323)

* Add enabled toggle to wallet settings view

* Add enabled toggle to Lightning settings view

* Remove redundant payment methods from store settings

* Rename Payment Methods to Payments

* Adapt tests

* Fix invoice state toggle on details page

* Add spacing on Lightning sett8ings page
This commit is contained in:
d11n 2022-01-19 12:58:02 +01:00 committed by GitHub
parent 51c486c15a
commit c419ad68bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 223 additions and 428 deletions

View File

@ -70,42 +70,42 @@ namespace BTCPayServer.Tests
Assert.Equal(3, invoice.CryptoInfo.Length); Assert.Equal(3, invoice.CryptoInfo.Length);
// Setup Lightning
var controller = user.GetController<UIStoresController>(); var controller = user.GetController<UIStoresController>();
var lightningVm = (LightningNodeViewModel)Assert.IsType<ViewResult>(await controller.SetupLightningNode(user.StoreId, cryptoCode)).Model; var lightningVm = (LightningNodeViewModel)Assert.IsType<ViewResult>(await controller.SetupLightningNode(user.StoreId, cryptoCode)).Model;
Assert.True(lightningVm.Enabled); Assert.True(lightningVm.Enabled);
var response = await controller.SetLightningNodeEnabled(user.StoreId, cryptoCode, false); var response = await controller.SetLightningNodeEnabled(user.StoreId, cryptoCode, false);
Assert.IsType<RedirectToActionResult>(response); Assert.IsType<RedirectToActionResult>(response);
// Get enabled state from overview action // Get enabled state from settings
PaymentMethodsViewModel paymentMethodsModel; LightningSettingsViewModel lnSettingsModel;
response = controller.PaymentMethods(); response = controller.LightningSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
paymentMethodsModel = (PaymentMethodsViewModel)Assert.IsType<ViewResult>(response).Model; lnSettingsModel = (LightningSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
var lnNode = paymentMethodsModel.LightningNodes.Find(node => node.CryptoCode == cryptoCode); Assert.NotNull(lnSettingsModel?.ConnectionString);
Assert.NotNull(lnNode); Assert.False(lnSettingsModel.Enabled);
Assert.False(lnNode.Enabled);
// Setup wallet
WalletSetupViewModel setupVm; WalletSetupViewModel setupVm;
var storeId = user.StoreId; var storeId = user.StoreId;
response = await controller.GenerateWallet(storeId, cryptoCode, WalletSetupMethod.GenerateOptions, new WalletSetupRequest()); response = await controller.GenerateWallet(storeId, cryptoCode, WalletSetupMethod.GenerateOptions, new WalletSetupRequest());
Assert.IsType<ViewResult>(response); Assert.IsType<ViewResult>(response);
// Get enabled state from overview action // Get enabled state from settings
response = controller.PaymentMethods(); response = controller.WalletSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
paymentMethodsModel = (PaymentMethodsViewModel)Assert.IsType<ViewResult>(response).Model; var onchainSettingsModel = (WalletSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
var derivationScheme = paymentMethodsModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode); Assert.NotNull(onchainSettingsModel?.DerivationScheme);
Assert.NotNull(derivationScheme); Assert.True(onchainSettingsModel.Enabled);
Assert.True(derivationScheme.Enabled);
// Disable wallet // Disable wallet
response = controller.SetWalletEnabled(storeId, cryptoCode, false).GetAwaiter().GetResult(); onchainSettingsModel.Enabled = false;
response = controller.UpdateWalletSettings(onchainSettingsModel).GetAwaiter().GetResult();
Assert.IsType<RedirectToActionResult>(response); Assert.IsType<RedirectToActionResult>(response);
response = controller.PaymentMethods(); response = controller.WalletSettings(user.StoreId, cryptoCode).GetAwaiter().GetResult();
paymentMethodsModel = (PaymentMethodsViewModel)Assert.IsType<ViewResult>(response).Model; onchainSettingsModel = (WalletSettingsViewModel)Assert.IsType<ViewResult>(response).Model;
derivationScheme = paymentMethodsModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode); Assert.NotNull(onchainSettingsModel?.DerivationScheme);
Assert.NotNull(derivationScheme); Assert.False(onchainSettingsModel.Enabled);
Assert.False(derivationScheme.Enabled);
var oldScheme = derivationScheme.Value; var oldScheme = onchainSettingsModel.DerivationScheme;
invoice = await user.BitPay.CreateInvoiceAsync( invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice new Invoice
@ -430,7 +430,7 @@ namespace BTCPayServer.Tests
s.GoToInvoiceCheckout(invoiceId); s.GoToInvoiceCheckout(invoiceId);
s.Driver.FindElement(By.ClassName("payment__currencies_noborder")); s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
s.GoToHome(); s.GoToHome();
s.GoToStore(StoreNavPages.PaymentMethods); s.GoToStore(StoreNavPages.Payment);
s.AddDerivationScheme("LTC"); s.AddDerivationScheme("LTC");
s.AddLightningNode(LightningConnectionType.CLightning); s.AddLightningNode(LightningConnectionType.CLightning);
//there should be three now //there should be three now

View File

@ -204,7 +204,7 @@ namespace BTCPayServer.Tests
s.GoToRegister(); s.GoToRegister();
s.RegisterNewUser(); s.RegisterNewUser();
s.CreateNewStore(); s.CreateNewStore();
s.GoToStore(StoreNavPages.PaymentMethods); s.GoToStore(StoreNavPages.Payment);
s.AddDerivationScheme(); s.AddDerivationScheme();
var invoiceId = s.CreateInvoice(0.001m, "BTC", "a@x.com"); var invoiceId = s.CreateInvoice(0.001m, "BTC", "a@x.com");
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId); var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);

View File

@ -289,7 +289,8 @@ namespace BTCPayServer.Tests
.GetAttribute("href"); .GetAttribute("href");
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}=", bip21); Assert.Contains($"{PayjoinClient.BIP21EndpointKey}=", bip21);
s.GoToWalletSettings(receiver.storeId, cryptoCode); s.GoToStore(receiver.storeId);
s.GoToWalletSettings(cryptoCode);
Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected); Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
var sender = s.CreateNewStore(); var sender = s.CreateNewStore();

View File

@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.BIP78.Sender; using BTCPayServer.BIP78.Sender;
@ -155,7 +156,7 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Id("StoreNav-StoreSettings")).Click(); Driver.FindElement(By.Id("StoreNav-StoreSettings")).Click();
Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.General.ToString()}")).Click(); Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.General.ToString()}")).Click();
var storeId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value"); var storeId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value");
Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.PaymentMethods.ToString()}")).Click(); Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.Payment.ToString()}")).Click();
if (keepId) if (keepId)
StoreId = storeId; StoreId = storeId;
return (name, storeId); return (name, storeId);
@ -164,7 +165,7 @@ namespace BTCPayServer.Tests
public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit) public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit)
{ {
var isImport = !string.IsNullOrEmpty(seed); var isImport = !string.IsNullOrEmpty(seed);
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click(); GoToWalletSettings(cryptoCode);
// Replace previous wallet case // Replace previous wallet case
if (Driver.PageSource.Contains("id=\"ChangeWalletLink\"")) if (Driver.PageSource.Contains("id=\"ChangeWalletLink\""))
@ -228,10 +229,7 @@ namespace BTCPayServer.Tests
/// <param name="derivationScheme"></param> /// <param name="derivationScheme"></param>
public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]") public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]")
{ {
if (Driver.PageSource.Contains($"id=\"Modify{cryptoCode}\"")) GoToWalletSettings(cryptoCode);
{
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
}
Driver.FindElement(By.Id("ImportWalletOptionsLink")).Click(); Driver.FindElement(By.Id("ImportWalletOptionsLink")).Click();
Driver.FindElement(By.Id("ImportXpubLink")).Click(); Driver.FindElement(By.Id("ImportXpubLink")).Click();
@ -254,11 +252,7 @@ namespace BTCPayServer.Tests
public void AddLightningNode(string cryptoCode = null, LightningConnectionType? connectionType = null, bool test = true) public void AddLightningNode(string cryptoCode = null, LightningConnectionType? connectionType = null, bool test = true)
{ {
cryptoCode ??= "BTC"; cryptoCode ??= "BTC";
if (Driver.PageSource.Contains($"id=\"Modify-Lightning{cryptoCode}\"")) Driver.FindElement(By.Id($"StoreNav-Lightning{cryptoCode}")).Click();
{
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
}
if (Driver.PageSource.Contains("id=\"SetupLightningNodeLink\"")) if (Driver.PageSource.Contains("id=\"SetupLightningNodeLink\""))
{ {
Driver.FindElement(By.Id("SetupLightningNodeLink")).Click(); Driver.FindElement(By.Id("SetupLightningNodeLink")).Click();
@ -392,22 +386,18 @@ namespace BTCPayServer.Tests
} }
} }
public void GoToWalletSettings(string storeId, string cryptoCode = "BTC") public void GoToWalletSettings(string cryptoCode = "BTC")
{ {
try Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
if (Driver.PageSource.Contains("id=\"SectionNav-Settings\""))
{ {
GoToStore(storeId, StoreNavPages.PaymentMethods); Driver.FindElement(By.Id("SectionNav-Settings")).Click();
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
}
catch (NoSuchElementException)
{
GoToWallet(new WalletId(storeId, cryptoCode), WalletsNavPages.Settings);
} }
} }
public void GoToLightningSettings(string cryptoCode = "BTC") public void GoToLightningSettings(string cryptoCode = "BTC")
{ {
GoToStore(StoreNavPages.PaymentMethods); GoToStore(StoreNavPages.Payment);
Driver.FindElement(By.Id($"StoreNav-Lightning{cryptoCode}")).Click(); Driver.FindElement(By.Id($"StoreNav-Lightning{cryptoCode}")).Click();
// if Lightning is already set up we need to navigate to the settings // if Lightning is already set up we need to navigate to the settings
if (Driver.PageSource.Contains("id=\"SectionNav-LightningSettings\"")) if (Driver.PageSource.Contains("id=\"SectionNav-LightningSettings\""))

View File

@ -335,6 +335,7 @@ namespace BTCPayServer.Tests
Assert.DoesNotContain("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource); Assert.DoesNotContain("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource);
} }
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]
public async Task CanCreateInvoiceInUI() public async Task CanCreateInvoiceInUI()
{ {
@ -402,18 +403,9 @@ namespace BTCPayServer.Tests
var alice = s.RegisterNewUser(true); var alice = s.RegisterNewUser(true);
(string storeName, string storeId) = s.CreateNewStore(); (string storeName, string storeId) = s.CreateNewStore();
var storeUrl = $"/stores/{storeId}"; var storeUrl = $"/stores/{storeId}";
var onchainHint = "Set up your wallet to receive payments at your store.";
var offchainHint = "A connection to a Lightning node is required to receive Lightning payments.";
// verify that hints are displayed on the store page s.GoToStore(StoreNavPages.Payment);
Assert.True(s.Driver.PageSource.Contains(onchainHint), "Wallet hint not present");
Assert.True(s.Driver.PageSource.Contains(offchainHint), "Lightning hint not present");
s.GoToStore(StoreNavPages.PaymentMethods);
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(offchainHint),
"Lightning hint should be present at this point");
// verify steps for wallet setup are displayed correctly // verify steps for wallet setup are displayed correctly
s.GoToStore(StoreNavPages.Dashboard); s.GoToStore(StoreNavPages.Dashboard);
@ -423,11 +415,8 @@ namespace BTCPayServer.Tests
// setup onchain wallet // setup onchain wallet
s.Driver.FindElement(By.Id("SetupGuide-Wallet")).Click(); s.Driver.FindElement(By.Id("SetupGuide-Wallet")).Click();
Thread.Sleep(10000);
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");
s.GoToStore(StoreNavPages.Dashboard); s.GoToStore(StoreNavPages.Dashboard);
Assert.True(s.Driver.FindElement(By.Id("SetupGuide-WalletDone")).Displayed); Assert.True(s.Driver.FindElement(By.Id("SetupGuide-WalletDone")).Displayed);
@ -438,8 +427,6 @@ 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");
s.ClickOnAllSectionLinks(); s.ClickOnAllSectionLinks();
@ -541,7 +528,7 @@ namespace BTCPayServer.Tests
s.CreateNewStore(); s.CreateNewStore();
s.AddDerivationScheme(); s.AddDerivationScheme();
s.Driver.FindElement(By.Id("SectionNav-Tokens")).Click(); s.GoToStore(StoreNavPages.Tokens);
s.Driver.FindElement(By.Id("CreateNewToken")).Click(); s.Driver.FindElement(By.Id("CreateNewToken")).Click();
s.Driver.FindElement(By.Id("RequestPairing")).Click(); s.Driver.FindElement(By.Id("RequestPairing")).Click();
var pairingCode = AssertUrlHasPairingCode(s); var pairingCode = AssertUrlHasPairingCode(s);
@ -794,7 +781,7 @@ namespace BTCPayServer.Tests
Assert.Contains(server.ServerUri.AbsoluteUri, s.Driver.PageSource); Assert.Contains(server.ServerUri.AbsoluteUri, s.Driver.PageSource);
TestLogs.LogInformation("Let's see if we can generate an event"); TestLogs.LogInformation("Let's see if we can generate an event");
s.GoToStore(StoreNavPages.PaymentMethods); s.GoToStore(StoreNavPages.Payment);
s.AddDerivationScheme(); s.AddDerivationScheme();
s.CreateInvoice(); s.CreateInvoice();
var request = await server.GetNextRequest(); var request = await server.GetNextRequest();
@ -859,9 +846,9 @@ namespace BTCPayServer.Tests
foreach (var isHotwallet in new[] { false, true }) foreach (var isHotwallet in new[] { false, true })
{ {
var cryptoCode = "BTC"; var cryptoCode = "BTC";
(_, string storeId) = s.CreateNewStore(); s.CreateNewStore();
s.GenerateWallet(cryptoCode, "melody lizard phrase voice unique car opinion merge degree evil swift cargo", privkeys: isHotwallet); s.GenerateWallet(cryptoCode, "melody lizard phrase voice unique car opinion merge degree evil swift cargo", privkeys: isHotwallet);
s.GoToWalletSettings(storeId, cryptoCode); s.GoToWalletSettings(cryptoCode);
if (isHotwallet) if (isHotwallet)
Assert.Contains("View seed", s.Driver.PageSource); Assert.Contains("View seed", s.Driver.PageSource);
else else
@ -916,10 +903,8 @@ namespace BTCPayServer.Tests
receiveAddr = s.Driver.FindElement(By.Id("address")).GetAttribute("value"); receiveAddr = s.Driver.FindElement(By.Id("address")).GetAttribute("value");
//change the wallet and ensure old address is not there and generating a new one does not result in the prev one //change the wallet and ensure old address is not there and generating a new one does not result in the prev one
s.GoToStore(storeId, StoreNavPages.PaymentMethods);
s.GenerateWallet(cryptoCode, "", true); s.GenerateWallet(cryptoCode, "", true);
s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click(); s.GoToWallet(null, WalletsNavPages.Receive);
s.Driver.FindElement(By.Id("SectionNav-Receive")).Click();
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click(); s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value")); Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value"));
@ -932,7 +917,7 @@ namespace BTCPayServer.Tests
var result = var result =
await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest)); await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
Assert.True(result.IsWatchOnly); Assert.True(result.IsWatchOnly);
s.GoToStore(storeId, StoreNavPages.PaymentMethods); s.GoToStore(storeId, StoreNavPages.Payment);
var mnemonic = s.GenerateWallet(cryptoCode, "", true, true); var mnemonic = s.GenerateWallet(cryptoCode, "", true, true);
//lets import and save private keys //lets import and save private keys
@ -953,7 +938,7 @@ namespace BTCPayServer.Tests
s.ClickOnAllSectionLinks(); s.ClickOnAllSectionLinks();
// Make sure wallet info is correct // Make sure wallet info is correct
s.GoToWalletSettings(storeId, cryptoCode); s.GoToWalletSettings(cryptoCode);
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(), Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(),
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value")); s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
Assert.Contains("m/84'/1'/0'", Assert.Contains("m/84'/1'/0'",
@ -1011,7 +996,7 @@ namespace BTCPayServer.Tests
Assert.Equal(parsedBip21.Address.ToString(), Assert.Equal(parsedBip21.Address.ToString(),
s.Driver.FindElement(By.Id("Outputs_0__DestinationAddress")).GetAttribute("value")); s.Driver.FindElement(By.Id("Outputs_0__DestinationAddress")).GetAttribute("value"));
s.GoToWalletSettings(storeId, cryptoCode); s.GoToWalletSettings(cryptoCode);
var settingsUrl = s.Driver.Url; var settingsUrl = s.Driver.Url;
s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click(); s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click();
s.Driver.FindElement(By.Id("ViewSeed")).Click(); s.Driver.FindElement(By.Id("ViewSeed")).Click();
@ -1035,12 +1020,12 @@ namespace BTCPayServer.Tests
using var s = CreateSeleniumTester(); using var s = CreateSeleniumTester();
await s.StartAsync(); await s.StartAsync();
s.RegisterNewUser(true); s.RegisterNewUser(true);
(_, string storeId) = s.CreateNewStore(); s.CreateNewStore();
const string cryptoCode = "BTC"; const string cryptoCode = "BTC";
var mnemonic = s.GenerateWallet(cryptoCode, "click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel"); var mnemonic = s.GenerateWallet(cryptoCode, "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.GoToWalletSettings(storeId, cryptoCode); s.GoToWalletSettings(cryptoCode);
Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(), Assert.Contains(mnemonic.DeriveExtKey().GetPublicKey().GetHDFingerPrint().ToString(),
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value")); s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).GetAttribute("value"));
Assert.Contains("m/84'/1'/0'", Assert.Contains("m/84'/1'/0'",
@ -1305,7 +1290,7 @@ namespace BTCPayServer.Tests
s.RegisterNewUser(true); s.RegisterNewUser(true);
s.CreateNewStore(); s.CreateNewStore();
s.GoToStore(StoreNavPages.PaymentMethods); s.GoToStore(StoreNavPages.Payment);
s.AddLightningNode(LightningConnectionType.CLightning, false); s.AddLightningNode(LightningConnectionType.CLightning, false);
s.GoToLightningSettings(); s.GoToLightningSettings();
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true); s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
@ -1348,7 +1333,7 @@ namespace BTCPayServer.Tests
s.RegisterNewUser(true); s.RegisterNewUser(true);
(_, string storeId) = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork; var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork;
s.GoToStore(StoreNavPages.PaymentMethods); s.GoToStore(StoreNavPages.Payment);
s.AddLightningNode(LightningConnectionType.CLightning, false); s.AddLightningNode(LightningConnectionType.CLightning, false);
s.GoToLightningSettings(); s.GoToLightningSettings();
// LNURL is false by default // LNURL is false by default
@ -1561,7 +1546,7 @@ namespace BTCPayServer.Tests
//ensure ln address is not available as Lightning is not enable //ensure ln address is not available as Lightning is not enable
s.Driver.AssertElementNotFound(By.Id("StoreNav-LightningAddress")); s.Driver.AssertElementNotFound(By.Id("StoreNav-LightningAddress"));
s.GoToStore(s.StoreId, StoreNavPages.PaymentMethods); s.GoToStore(s.StoreId, StoreNavPages.Payment);
s.AddLightningNode(LightningConnectionType.LndREST, false); s.AddLightningNode(LightningConnectionType.LndREST, false);
//ensure ln address is not available as lnurl is not configured //ensure ln address is not available as lnurl is not configured
s.Driver.AssertElementNotFound(By.Id("StoreNav-LightningAddress")); s.Driver.AssertElementNotFound(By.Id("StoreNav-LightningAddress"));

View File

@ -133,13 +133,13 @@ namespace BTCPayServer.Tests
}); });
} }
public async Task ModifyPayment(Action<PaymentMethodsViewModel> modify) public async Task ModifyPayment(Action<PaymentViewModel> modify)
{ {
var storeController = GetController<UIStoresController>(); var storeController = GetController<UIStoresController>();
var response = storeController.PaymentMethods(); var response = storeController.Payment();
PaymentMethodsViewModel paymentMethods = (PaymentMethodsViewModel)((ViewResult)response).Model; PaymentViewModel payment = (PaymentViewModel)((ViewResult)response).Model;
modify(paymentMethods); modify(payment);
await storeController.PaymentMethods(paymentMethods); await storeController.Payment(payment);
} }
public async Task ModifyWalletSettings(Action<WalletSettingsViewModel> modify) public async Task ModifyWalletSettings(Action<WalletSettingsViewModel> modify)

View File

@ -275,11 +275,11 @@ namespace BTCPayServer.Tests
// Set tolerance to 50% // Set tolerance to 50%
var stores = user.GetController<UIStoresController>(); var stores = user.GetController<UIStoresController>();
var response = stores.PaymentMethods(); var response = stores.Payment();
var vm = Assert.IsType<PaymentMethodsViewModel>(Assert.IsType<ViewResult>(response).Model); var vm = Assert.IsType<PaymentViewModel>(Assert.IsType<ViewResult>(response).Model);
Assert.Equal(0.0, vm.PaymentTolerance); Assert.Equal(0.0, vm.PaymentTolerance);
vm.PaymentTolerance = 50.0; vm.PaymentTolerance = 50.0;
Assert.IsType<RedirectToActionResult>(stores.PaymentMethods(vm).Result); Assert.IsType<RedirectToActionResult>(stores.Payment(vm).Result);
var invoice = user.BitPay.CreateInvoice( var invoice = user.BitPay.CreateInvoice(
new Invoice() new Invoice()
@ -417,7 +417,7 @@ namespace BTCPayServer.Tests
var user = tester.NewAccount(); var user = tester.NewAccount();
user.GrantAccess(true); user.GrantAccess(true);
var storeController = user.GetController<UIStoresController>(); var storeController = user.GetController<UIStoresController>();
var storeResponse = storeController.PaymentMethods(); var storeResponse = storeController.Payment();
Assert.IsType<ViewResult>(storeResponse); Assert.IsType<ViewResult>(storeResponse);
Assert.IsType<ViewResult>(await storeController.SetupLightningNode(user.StoreId, "BTC")); Assert.IsType<ViewResult>(await storeController.SetupLightningNode(user.StoreId, "BTC"));
@ -441,11 +441,11 @@ namespace BTCPayServer.Tests
new LightningNodeViewModel { ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri }, new LightningNodeViewModel { ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri },
"save", "BTC").GetAwaiter().GetResult()); "save", "BTC").GetAwaiter().GetResult());
storeResponse = storeController.PaymentMethods(); storeResponse = storeController.LightningSettings(user.StoreId, "BTC").GetAwaiter().GetResult();
var storeVm = var storeVm =
Assert.IsType<PaymentMethodsViewModel>(Assert Assert.IsType<LightningSettingsViewModel>(Assert
.IsType<ViewResult>(storeResponse).Model); .IsType<ViewResult>(storeResponse).Model);
Assert.Single(storeVm.LightningNodes.Where(l => !string.IsNullOrEmpty(l.Address))); Assert.NotEmpty(storeVm.ConnectionString);
} }
[Fact(Timeout = 60 * 2 * 1000)] [Fact(Timeout = 60 * 2 * 1000)]

View File

@ -35,7 +35,7 @@
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a asp-area="" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(StoreNavPages.PaymentMethods) @ViewData.IsActivePage(StoreNavPages.Rates) @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance) @ViewData.IsActivePage(StoreNavPages.General) @ViewData.IsActivePage(StoreNavPages.Tokens) @ViewData.IsActivePage(StoreNavPages.Users) @ViewData.IsActivePage(StoreNavPages.Integrations) @ViewData.IsActivePage(StoreNavPages.Webhooks)" id="StoreNav-StoreSettings"> <a asp-area="" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(StoreNavPages.Payment) @ViewData.IsActivePage(StoreNavPages.Rates) @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance) @ViewData.IsActivePage(StoreNavPages.General) @ViewData.IsActivePage(StoreNavPages.Tokens) @ViewData.IsActivePage(StoreNavPages.Users) @ViewData.IsActivePage(StoreNavPages.Integrations) @ViewData.IsActivePage(StoreNavPages.Webhooks)" id="StoreNav-StoreSettings">
<vc:icon symbol="settings"/> <vc:icon symbol="settings"/>
<span>Settings</span> <span>Settings</span>
</a> </a>
@ -65,7 +65,7 @@
} }
else else
{ {
<a asp-area="" asp-controller="UIStores" asp-action="SetupWallet" asp-route-cryptoCode="@scheme.Crypto" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.OnchainSettings)" id="@($"StoreNav-Modify{scheme.Crypto}")"> <a asp-area="" asp-controller="UIStores" asp-action="SetupWallet" asp-route-cryptoCode="@scheme.Crypto" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.OnchainSettings)" id="@($"StoreNav-Wallet{scheme.Crypto}")">
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "disabled")"></span> <span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "disabled")"></span>
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} " : "")Wallet</span> <span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} " : "")Wallet</span>
</a> </a>

View File

@ -101,7 +101,7 @@ namespace BTCPayServer.Controllers
{ {
StoreId = store.Id, StoreId = store.Id,
StoreName = store.StoreName, StoreName = store.StoreName,
StoreLink = Url.Action(nameof(UIStoresController.PaymentMethods), "UIStores", new { storeId = store.Id }), StoreLink = Url.Action(nameof(UIStoresController.Payment), "UIStores", new { storeId = store.Id }),
PaymentRequestLink = Url.Action(nameof(UIPaymentRequestController.ViewPaymentRequest), "UIPaymentRequest", new { payReqId = invoice.Metadata.PaymentRequestId }), PaymentRequestLink = Url.Action(nameof(UIPaymentRequestController.ViewPaymentRequest), "UIPaymentRequest", new { payReqId = invoice.Metadata.PaymentRequestId }),
Id = invoice.Id, Id = invoice.Id,
State = invoiceState, State = invoiceState,
@ -898,7 +898,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Severity = StatusMessageModel.StatusSeverity.Error, Severity = StatusMessageModel.StatusSeverity.Error,
Html = $"To create an invoice, you need to <a href='{Url.Action(nameof(UIStoresController.PaymentMethods), "UIStores", new { storeId = store.Id })}' class='alert-link'>set up a payment method</a> first", Html = $"To create an invoice, you need to <a href='{Url.Action(nameof(UIStoresController.Payment), "UIStores", new { storeId = store.Id })}' class='alert-link'>set up a payment method</a> first",
AllowDismiss = false AllowDismiss = false
}); });
return View(model); return View(model);

View File

@ -463,7 +463,7 @@ namespace BTCPayServer
Message = "LNURL is required for lightning addresses but has not yet been enabled.", Message = "LNURL is required for lightning addresses but has not yet been enabled.",
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction("PaymentMethods", "UIStores", new { storeId }); return RedirectToAction("Payment", "UIStores", new { storeId });
} }
var lightningAddressSettings = await _settingsRepository.GetSettingAsync<LightningAddressSettings>() ?? var lightningAddressSettings = await _settingsRepository.GetSettingAsync<LightningAddressSettings>() ??
new LightningAddressSettings(); new LightningAddressSettings();

View File

@ -74,7 +74,7 @@ namespace BTCPayServer.Controllers
Message = "You must enable at least one payment method before creating a pull payment.", Message = "You must enable at least one payment method before creating a pull payment.",
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction("PaymentMethods", "UIStores", new { storeId }); return RedirectToAction("Payment", "UIStores", new { storeId });
} }
return View(new NewPullPaymentModel return View(new NewPullPaymentModel
{ {
@ -439,7 +439,7 @@ namespace BTCPayServer.Controllers
Message = "You must enable at least one payment method before creating a payout.", Message = "You must enable at least one payment method before creating a payout.",
Severity = StatusMessageModel.StatusSeverity.Error Severity = StatusMessageModel.StatusSeverity.Error
}); });
return RedirectToAction("PaymentMethods", "UIStores", new { storeId }); return RedirectToAction("Payment", "UIStores", new { storeId });
} }
var vm = this.ParseListQuery(new PayoutsModel var vm = this.ParseListQuery(new PayoutsModel

View File

@ -167,7 +167,7 @@ namespace BTCPayServer.Controllers
await _Repo.UpdateStore(store); await _Repo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning node updated."; TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning node updated.";
return RedirectToAction(nameof(PaymentMethods), new { storeId }); return RedirectToAction(nameof(LightningSettings), new { storeId, cryptoCode });
case "test": case "test":
var handler = _ServiceProvider.GetRequiredService<LightningLikePaymentHandler>(); var handler = _ServiceProvider.GetRequiredService<LightningLikePaymentHandler>();
@ -201,10 +201,13 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
var lightning = GetExistingLightningSupportedPaymentMethod(cryptoCode, store);
var vm = new LightningSettingsViewModel var vm = new LightningSettingsViewModel
{ {
CryptoCode = cryptoCode, CryptoCode = cryptoCode,
StoreId = storeId, StoreId = storeId,
Enabled = !excludeFilters.Match(lightning.PaymentId),
LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate, LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate,
LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi, LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi,
LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints, LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints,
@ -212,7 +215,6 @@ namespace BTCPayServer.Controllers
}; };
await SetExistingValues(store, vm); await SetExistingValues(store, vm);
var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store);
var lnSet = lightning != null; var lnSet = lightning != null;
if (lnSet) if (lnSet)
{ {
@ -297,10 +299,10 @@ namespace BTCPayServer.Controllers
{ {
await _Repo.UpdateStore(store); await _Repo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning settings successfully updated"; TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning settings successfully updated.";
} }
return RedirectToAction(nameof(PaymentMethods), new { vm.StoreId }); return RedirectToAction(nameof(LightningSettings), new { vm.StoreId, vm.CryptoCode });
} }
[HttpPost("{storeId}/lightning/{cryptoCode}/status")] [HttpPost("{storeId}/lightning/{cryptoCode}/status")]
@ -329,7 +331,7 @@ namespace BTCPayServer.Controllers
await _Repo.UpdateStore(store); await _Repo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning payments are now {(enabled ? "enabled" : "disabled")} for this store."; TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning payments are now {(enabled ? "enabled" : "disabled")} for this store.";
return RedirectToAction(nameof(PaymentMethods), new { storeId }); return RedirectToAction(nameof(LightningSettings), new { storeId, cryptoCode });
} }
private async Task<bool> CanUseInternalLightning() private async Task<bool> CanUseInternalLightning()

View File

@ -178,7 +178,7 @@ namespace BTCPayServer.Controllers
TempData[WellKnownTempData.SuccessMessage] = $"Wallet settings for {network.CryptoCode} have been updated."; TempData[WellKnownTempData.SuccessMessage] = $"Wallet settings for {network.CryptoCode} have been updated.";
// This is success case when derivation scheme is added to the store // This is success case when derivation scheme is added to the store
return RedirectToAction(nameof(PaymentMethods), new { storeId = vm.StoreId }); return RedirectToAction(nameof(WalletSettings), new { storeId = vm.StoreId, cryptoCode = vm.CryptoCode });
} }
return ConfirmAddresses(vm, strategy); return ConfirmAddresses(vm, strategy);
} }
@ -362,7 +362,7 @@ namespace BTCPayServer.Controllers
TempData[WellKnownTempData.SuccessMessage] = $"Wallet settings for {network.CryptoCode} have been updated."; TempData[WellKnownTempData.SuccessMessage] = $"Wallet settings for {network.CryptoCode} have been updated.";
return RedirectToAction(nameof(PaymentMethods), new { storeId }); return RedirectToAction(nameof(Payment), new { storeId });
} }
[HttpGet("{storeId}/onchain/{cryptoCode}/settings")] [HttpGet("{storeId}/onchain/{cryptoCode}/settings")]
@ -381,6 +381,7 @@ namespace BTCPayServer.Controllers
} }
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
(bool canUseHotWallet, bool rpcImport) = await CanUseHotWallet(); (bool canUseHotWallet, bool rpcImport) = await CanUseHotWallet();
var client = _ExplorerProvider.GetExplorerClient(network); var client = _ExplorerProvider.GetExplorerClient(network);
@ -389,6 +390,7 @@ namespace BTCPayServer.Controllers
StoreId = storeId, StoreId = storeId,
CryptoCode = cryptoCode, CryptoCode = cryptoCode,
WalletId = new WalletId(storeId, cryptoCode), WalletId = new WalletId(storeId, cryptoCode),
Enabled = !excludeFilters.Match(derivation.PaymentId),
Network = network, Network = network,
IsHotWallet = derivation.IsHotWallet, IsHotWallet = derivation.IsHotWallet,
Source = derivation.Source, Source = derivation.Source,
@ -447,8 +449,19 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
} }
bool needUpdate = false; var storeBlob = store.GetStoreBlob();
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
var currentlyEnabled = !excludeFilters.Match(derivation.PaymentId);
bool enabledChanged = currentlyEnabled != vm.Enabled;
bool needUpdate = enabledChanged;
string errorMessage = null; string errorMessage = null;
if (enabledChanged)
{
storeBlob.SetExcluded(derivation.PaymentId, !vm.Enabled);
store.SetStoreBlob(storeBlob);
}
if (derivation.Label != vm.Label) if (derivation.Label != vm.Label)
{ {
needUpdate = true; needUpdate = true;
@ -466,8 +479,8 @@ namespace BTCPayServer.Controllers
for (int i = 0; i < derivation.AccountKeySettings.Length; i++) for (int i = 0; i < derivation.AccountKeySettings.Length; i++)
{ {
KeyPath accountKeyPath = null; KeyPath accountKeyPath;
HDFingerprint? rootFingerprint = null; HDFingerprint? rootFingerprint;
try try
{ {
@ -512,7 +525,14 @@ namespace BTCPayServer.Controllers
if (string.IsNullOrEmpty(errorMessage)) if (string.IsNullOrEmpty(errorMessage))
{ {
TempData[WellKnownTempData.SuccessMessage] = "Wallet settings successfully updated"; var successMessage = "Wallet settings successfully updated.";
if (enabledChanged)
{
_EventAggregator.Publish(new WalletChangedEvent { WalletId = new WalletId(vm.StoreId, vm.CryptoCode) });
successMessage += $" {vm.CryptoCode} on-chain payments are now {(vm.Enabled ? "enabled" : "disabled")} for this store.";
}
TempData[WellKnownTempData.SuccessMessage] = successMessage;
} }
else else
{ {
@ -690,40 +710,6 @@ namespace BTCPayServer.Controllers
}); });
} }
[HttpPost("{storeId}/onchain/{cryptoCode}/status")]
public async Task<IActionResult> SetWalletEnabled(string storeId, string cryptoCode, bool enabled)
{
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
if (checkResult != null)
{
return checkResult;
}
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
if (derivation == null)
{
return NotFound();
}
var wallet = _WalletProvider.GetWallet(network);
if (wallet == null)
{
return NotFound();
}
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
var storeBlob = store.GetStoreBlob();
storeBlob.SetExcluded(paymentMethodId, !enabled);
store.SetStoreBlob(storeBlob);
await _Repo.UpdateStore(store);
_EventAggregator.Publish(new WalletChangedEvent { WalletId = new WalletId(storeId, cryptoCode) });
TempData[WellKnownTempData.SuccessMessage] =
$"{network.CryptoCode} on-chain payments are now {(enabled ? "enabled" : "disabled")} for this store.";
return RedirectToAction(nameof(PaymentMethods), new { storeId });
}
[HttpPost("{storeId}/onchain/{cryptoCode}/delete")] [HttpPost("{storeId}/onchain/{cryptoCode}/delete")]
public async Task<IActionResult> ConfirmDeleteWallet(string storeId, string cryptoCode) public async Task<IActionResult> ConfirmDeleteWallet(string storeId, string cryptoCode)
{ {
@ -748,7 +734,7 @@ namespace BTCPayServer.Controllers
TempData[WellKnownTempData.SuccessMessage] = TempData[WellKnownTempData.SuccessMessage] =
$"On-Chain payment for {network.CryptoCode} has been removed."; $"On-Chain payment for {network.CryptoCode} has been removed.";
return RedirectToAction(nameof(PaymentMethods), new { storeId }); return RedirectToAction(nameof(Payment), new { storeId });
} }
private IActionResult ConfirmAddresses(WalletSetupViewModel vm, DerivationSchemeSettings strategy) private IActionResult ConfirmAddresses(WalletSetupViewModel vm, DerivationSchemeSettings strategy)

View File

@ -589,19 +589,17 @@ namespace BTCPayServer.Controllers
} }
} }
[HttpGet("{storeId}/payment-methods")] [HttpGet("{storeId}/payment")]
public IActionResult PaymentMethods() public IActionResult Payment()
{ {
var store = HttpContext.GetStoreData(); var store = HttpContext.GetStoreData();
if (store == null) if (store == null)
return NotFound(); return NotFound();
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
var vm = new PaymentMethodsViewModel var vm = new PaymentViewModel
{ {
Id = store.Id, Id = store.Id,
HintWallet = storeBlob.Hints.Wallet,
HintLightning = storeBlob.Hints.Lightning,
NetworkFeeMode = storeBlob.NetworkFeeMode, NetworkFeeMode = storeBlob.NetworkFeeMode,
AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice, AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice,
PaymentTolerance = storeBlob.PaymentTolerance, PaymentTolerance = storeBlob.PaymentTolerance,
@ -609,17 +607,11 @@ namespace BTCPayServer.Controllers
DefaultCurrency = storeBlob.DefaultCurrency DefaultCurrency = storeBlob.DefaultCurrency
}; };
AddPaymentMethods(store, storeBlob,
out var derivationSchemes, out var lightningNodes);
vm.DerivationSchemes = derivationSchemes;
vm.LightningNodes = lightningNodes;
return View(vm); return View(vm);
} }
[HttpPost("{storeId}/payment-methods")] [HttpPost("{storeId}/payment")]
public async Task<IActionResult> PaymentMethods(PaymentMethodsViewModel model, string command = null) public async Task<IActionResult> Payment(PaymentViewModel model, string command = null)
{ {
bool needUpdate = false; bool needUpdate = false;
var blob = CurrentStore.GetStoreBlob(); var blob = CurrentStore.GetStoreBlob();
@ -641,7 +633,7 @@ namespace BTCPayServer.Controllers
TempData[WellKnownTempData.SuccessMessage] = "Payment settings successfully updated"; TempData[WellKnownTempData.SuccessMessage] = "Payment settings successfully updated";
} }
return RedirectToAction(nameof(PaymentMethods), new return RedirectToAction(nameof(Payment), new
{ {
storeId = CurrentStore.Id storeId = CurrentStore.Id
}); });

View File

@ -1,16 +1,11 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
namespace BTCPayServer.Models.StoreViewModels namespace BTCPayServer.Models.StoreViewModels
{ {
public class PaymentMethodsViewModel public class PaymentViewModel
{ {
public string Id { get; set; } public string Id { get; set; }
public List<StoreDerivationScheme> DerivationSchemes { get; set; }
public List<StoreLightningNode> LightningNodes { get; set; }
public bool HintWallet { get; set; }
public bool HintLightning { get; set; }
[Display(Name = "Allow anyone to create invoice")] [Display(Name = "Allow anyone to create invoice")]
public bool AnyoneCanCreateInvoice { get; set; } public bool AnyoneCanCreateInvoice { get; set; }

View File

@ -10,6 +10,7 @@ namespace BTCPayServer.Models.StoreViewModels
public WalletId WalletId { get; set; } public WalletId WalletId { get; set; }
public string StoreId { get; set; } public string StoreId { get; set; }
public bool IsHotWallet { get; set; } public bool IsHotWallet { get; set; }
public bool Enabled { get; set; }
public bool CanUsePayJoin { get; set; } public bool CanUsePayJoin { get; set; }
[Display(Name = "Enable Payjoin/P2EP")] [Display(Name = "Enable Payjoin/P2EP")]

View File

@ -30,7 +30,7 @@
{ {
if (!isLightningEnabled) if (!isLightningEnabled)
{ {
<a asp-action="PaymentMethods" asp-controller="UIStores" asp-route-storeId="@store.Id" class="btn btn-link p-0"> <a asp-action="Payment" asp-controller="UIStores" asp-route-storeId="@store.Id" class="btn btn-link p-0">
You need to setup Lightning first You need to setup Lightning first
</a> </a>
} }

View File

@ -79,7 +79,7 @@
<td> <td>
@if (app.IsOwner) @if (app.IsOwner)
{ {
<span><a asp-action="PaymentMethods" asp-controller="UIStores" asp-route-storeId="@app.StoreId">@app.StoreName</a></span> <span><a asp-action="Payment" asp-controller="UIStores" asp-route-storeId="@app.StoreId">@app.StoreName</a></span>
} }
else else
{ {

View File

@ -31,7 +31,7 @@
{ {
<div class="alert alert-warning text-center sticky-top mb-0 rounded-0" role="alert"> <div class="alert alert-warning text-center sticky-top mb-0 rounded-0" role="alert">
LNURL is not enabled on your store, which this print feature relies on. LNURL is not enabled on your store, which this print feature relies on.
<a asp-action="PaymentMethods" asp-controller="UIStores" asp-route-storeId="@Model.Store.Id" class="alert-link p-0"> <a asp-action="Payment" asp-controller="UIStores" asp-route-storeId="@Model.Store.Id" class="alert-link p-0">
Enable LNURL Enable LNURL
</a> </a>
</div> </div>

View File

@ -14,18 +14,17 @@
@section PageFootContent { @section PageFootContent {
<script> <script>
const alertClasses = { "Settled (marked)": 'success', "Invalid (marked)": 'danger' }
function changeInvoiceState(invoiceId, newState) { function changeInvoiceState(invoiceId, newState) {
var toggleButton = $("#markStatusDropdownMenuButton"); console.log(invoiceId, newState)
const toggleButton = $("#markStatusDropdownMenuButton");
toggleButton.attr("disabled", "disabled"); toggleButton.attr("disabled", "disabled");
$.post(invoiceId + "/changestate/" + newState) $.post(`${invoiceId}/changestate/${newState}`)
.done(function (data) { .done(({ statusString }) => {
var alertClassModifier = { const alertClass = alertClasses[statusString];
"settled (marked)": 'success', toggleButton.replaceWith(`<span class="fs-6 fw-normal badge bg-${alertClass}">${statusString} <span class="fa fa-check"></span></span>`);
"invalid (marked)": 'danger'
}[data.statusString];
var statusHtml = "<span class='fs-6 fw-normal badge bg-" + alertClassModifier + "'>" + data.statusString + " <span class='fa fa-check'></span></span>"
toggleButton.replaceWith(statusHtml);
}) })
.fail(function () { .fail(function () {
toggleButton.removeAttr("disabled"); toggleButton.removeAttr("disabled");
@ -33,14 +32,11 @@
}); });
} }
document.addEventListener("DOMContentLoaded", function () { delegate('click', '[data-change-invoice-status-button]', e => {
$("[data-change-invoice-status-button]").click(function (e) { const button = e.target.closest('[data-change-invoice-status-button]')
var id = e.currentTarget.getAttribute('data-id'); const { id, status } = button.dataset
var status = e.currentTarget.getAttribute('data-status'); changeInvoiceState(id, status)
})
changeInvoiceState(id, status);
});
});
</script> </script>
} }

View File

@ -63,7 +63,7 @@ else
} }
else else
{ {
<a asp-controller="UIStores" asp-action="PaymentMethods" asp-route-storeId="@Model.StoreId" id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center order-1"> <a asp-controller="UIStores" asp-action="Payment" asp-route-storeId="@Model.StoreId" id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<vc:icon symbol="new-wallet"/> <vc:icon symbol="new-wallet"/>
<div class="content"> <div class="content">
<h5 class="mb-0">Set up a wallet</h5> <h5 class="mb-0">Set up a wallet</h5>
@ -95,7 +95,7 @@ else
} }
else else
{ {
<a asp-controller="UIStores" asp-action="PaymentMethods" asp-route-storeId="@Model.StoreId" id="SetupGuide-Lightning" class="list-group-item list-group-item-action d-flex align-items-center order-1"> <a asp-controller="UIStores" asp-action="Payment" asp-route-storeId="@Model.StoreId" id="SetupGuide-Lightning" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<vc:icon symbol="new-wallet"/> <vc:icon symbol="new-wallet"/>
<div class="content"> <div class="content">
<h5 class="mb-0">Set up a Lightning node</h5> <h5 class="mb-0">Set up a Lightning node</h5>

View File

@ -10,7 +10,7 @@
<div class="col-lg-10 col-xl-9"> <div class="col-lg-10 col-xl-9">
<div class="mb-5"> <div class="mb-5">
<h4 class="mb-3">@ViewData["Title"]</h4> <h4 class="mb-3">@ViewData["Title"]</h4>
<div class="text-break"> <div class="text-break mb-3">
<span class="me-2">@Model.LightningNodeType Node</span> <span class="me-2">@Model.LightningNodeType Node</span>
@if (Model.LightningNodeType != LightningNodeType.Internal) @if (Model.LightningNodeType != LightningNodeType.Internal)
{ {
@ -39,6 +39,19 @@
Change connection Change connection
</a> </a>
</div> </div>
<form method="post"
asp-action="SetLightningNodeEnabled"
asp-route-cryptoCode="@Model.CryptoCode"
asp-route-storeId="@Model.StoreId"
class="d-flex align-items-center"
style="min-width:7rem">
<button type="submit" class="btcpay-toggle me-2 @if (Model.Enabled) { @("btcpay-toggle--active") }" name="Enabled" value="@(Model.Enabled ? "false" : "true")" id="@($"{Model.CryptoCode}LightningEnabled")">@(Model.Enabled ? "Disable" : "Enable")</button>
<span>
Enabled
</span>
</form>
<form method="post" class="mt-n2 text-center"> <form method="post" class="mt-n2 text-center">
<div class="text-start"> <div class="text-start">
<h4 class="mt-5 mb-3">Payment</h4> <h4 class="mt-5 mb-3">Payment</h4>

View File

@ -0,0 +1,68 @@
@model PaymentViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Payment, "Wallets", Context.GetStoreData().Id);
}
<div class="row">
<div class="col-lg-10 col-xl-9">
@if (!ViewContext.ModelState.IsValid)
{
<div asp-validation-summary="All" class="text-danger"></div>
}
<form method="post">
<h3 class="mb-3">Payment</h3>
<div class="form-group">
<label asp-for="DefaultCurrency" class="form-label"></label>
<input asp-for="DefaultCurrency" class="form-control" style="max-width:10ch;" />
<span asp-validation-for="DefaultCurrency" class="text-danger"></span>
</div>
<div class="form-group d-flex align-items-center">
<input asp-for="AnyoneCanCreateInvoice" type="checkbox" class="btcpay-toggle me-2"/>
<label asp-for="AnyoneCanCreateInvoice" class="form-label mb-0 me-1"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
</div>
<div class="form-group mt-4">
<label asp-for="NetworkFeeMode" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#add-network-fee-to-invoice-vary-with-mining-fees" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<select asp-for="NetworkFeeMode" class="form-select">
<option value="MultiplePaymentsOnly">... only if the customer makes more than one payment for the invoice</option>
<option value="Always">... on every payment</option>
<option value="Never">Never add network fee</option>
</select>
</div>
<div class="form-group">
<label asp-for="InvoiceExpiration" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#invoice-expires-if-the-full-amount-has-not-been-paid-after-minutes" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<div class="input-group">
<input asp-for="InvoiceExpiration" class="form-control" style="max-width:10ch;"/>
<span class="input-group-text">minutes</span>
</div>
<span asp-validation-for="InvoiceExpiration" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PaymentTolerance" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-paid-even-if-the-paid-amount-is-less-than-expected" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<div class="input-group">
<input asp-for="PaymentTolerance" class="form-control" style="max-width:10ch;"/>
<span class="input-group-text">percent</span>
</div>
<span asp-validation-for="PaymentTolerance" class="text-danger"></span>
</div>
<button name="command" type="submit" class="btn btn-primary px-4 mt-3" value="Save" id="Save">Save</button>
</form>
</div>
</div>
@section PageFootContent {
<partial name="_ValidationScriptsPartial" />
}

View File

@ -1,241 +0,0 @@
@using System.Text.RegularExpressions
@using BTCPayServer.Lightning
@model PaymentMethodsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Wallets", Context.GetStoreData().Id);
}
<div class="row">
<div class="col-lg-10 col-xl-9">
@if (!ViewContext.ModelState.IsValid)
{
<div asp-validation-summary="All" class="text-danger"></div>
}
<div class="mb-5">
<h3 class="mb-3">Wallet</h3>
@if (Model.HintWallet)
{
<p>Set up your wallet to receive payments at your store.</p>
}
<ul class="list-group mb-3">
@foreach (var scheme in Model.DerivationSchemes.OrderBy(scheme => scheme.Collapsed))
{
var isSetUp = !string.IsNullOrWhiteSpace(scheme.Value);
<li class="list-group-item bg-tile @(scheme.Collapsed ? "assets-collapsed": "")">
<div class="d-flex align-items-center">
<span class="d-flex flex-wrap flex-fill flex-column flex-sm-row">
<strong class="me-3">@scheme.Crypto</strong>
@if (isSetUp)
{
<span title="@scheme.Value" data-bs-toggle="tooltip" class="d-flex me-3">
<span class="text-truncate text-secondary" style="max-width:8ch">@scheme.Value</span>
@if (scheme.Value.Length > 20)
{
<span class="text-nowrap text-secondary">@Regex.Match(scheme.Value, @"((?:-\[(?:[^\]])+\])+|\S{6})$").Value</span>
}
</span>
@if (scheme.WalletSupported)
{
<a asp-action="WalletTransactions" asp-controller="UIWallets" asp-route-walletId="@scheme.WalletId" class="text-secondary me-3">Manage Wallet</a>
}
}
</span>
<span class="d-flex align-items-center fw-semibold">
@if (isSetUp)
{
<form method="post"
asp-action="SetWalletEnabled"
asp-route-cryptoCode="@scheme.Crypto"
asp-route-storeId="@Model.Id"
class="d-flex align-items-center"
style="min-width:7rem">
<button type="submit" class="btcpay-toggle me-2 @if (scheme.Enabled) { @("btcpay-toggle--active") }" name="Enabled" value="@(scheme.Enabled ? "false" : "true")" id="@($"{scheme.Crypto}WalletEnabled")">@(scheme.Enabled ? "Disable" : "Enable")</button>
@if (scheme.Enabled)
{
<span class="text-primary">
Enabled
</span>
}
else
{
<span class="text-muted">
Disabled
</span>
}
</form>
<span class="text-light mx-2">|</span>
<a asp-action="WalletSettings" asp-route-cryptoCode="@scheme.Crypto" asp-route-storeId="@Model.Id" id="@($"Modify{scheme.Crypto}")" class="btn btn-link px-1 py-1 fw-semibold">
Settings
</a>
}
else
{
<a asp-action="SetupWallet" asp-route-cryptoCode="@scheme.Crypto" asp-route-storeId="@Model.Id" id="@($"Modify{scheme.Crypto}")" class="btn btn-primary btn-sm ms-4 px-3 py-1 fw-semibold">
Setup
</a>
}
</span>
</div>
</li>
}
</ul>
@if (Model.DerivationSchemes.Any(scheme => scheme.Collapsed))
{
<script>
document.addEventListener("DOMContentLoaded", (event) => {
var collapsed = document.getElementsByClassName("assets-collapsed");
for (let collapsedElement of collapsed) {
collapsedElement.style.display = 'none';
}
document
.getElementById("toggle-assets")
.addEventListener("click", function (){
var collapsed = document.getElementsByClassName("assets-collapsed");
for (let collapsedElement of collapsed) {
collapsedElement.style.display = 'block';
}
document.getElementById("toggle-assets").style.display="none";
});
});
</script>
<button class="btn btn-link text-secondary mb-3 p-0 only-for-js" id="toggle-assets" type="button">Show additional assets</button>
}
</div>
<div class="mb-5">
<h3 class="mb-3">Lightning</h3>
@if (Model.HintLightning)
{
<p>A connection to a Lightning node is required to receive Lightning payments.</p>
}
<ul class="list-group mb-3">
@foreach (var scheme in Model.LightningNodes)
{
var isSetUp = !string.IsNullOrWhiteSpace(scheme.Address);
<li class="list-group-item bg-tile">
<div class="d-flex align-items-center">
<span class="d-flex flex-wrap flex-fill flex-column flex-sm-row">
<strong class="me-3">@scheme.CryptoCode</strong>
@if (isSetUp)
{
<span class="text-truncate text-secondary me-3" style="max-width:150px;">
@if (LightningConnectionString.TryParse(scheme.Address, out var cs))
{
<span>@typeof(LightningConnectionType).DisplayName(cs.ConnectionType.ToString())</span>
<span>@cs.BaseUri.Host</span>
}
else
{
@scheme.Address
}
</span>
<a class="text-secondary me-3"
asp-controller="UIPublicLightningNodeInfo"
asp-action="ShowLightningNodeInfo"
asp-route-cryptoCode="@scheme.CryptoCode"
asp-route-storeId="@Model.Id"
target="_blank">
Public Node Info
</a>
}
</span>
<span class="d-flex align-items-center fw-semibold">
@if (isSetUp)
{
<form method="post"
asp-action="SetLightningNodeEnabled"
asp-route-cryptoCode="@scheme.CryptoCode"
asp-route-storeId="@Model.Id"
class="d-flex align-items-center"
style="min-width:7rem">
<button type="submit" class="btcpay-toggle me-2 @if (scheme.Enabled) { @("btcpay-toggle--active") }" name="Enabled" value="@(scheme.Enabled ? "false" : "true")" id="@($"{scheme.CryptoCode}LightningEnabled")">@(scheme.Enabled ? "Disable" : "Enable")e</button>
@if (scheme.Enabled)
{
<span class="text-primary">
Enabled
</span>
}
else
{
<span class="text-muted">
Disabled
</span>
}
</form>
<span class="text-light mx-2">|</span>
<a asp-action="LightningSettings" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Id" id="@($"Modify-Lightning{scheme.CryptoCode}")" class="btn btn-link px-1 py-1 fw-semibold">
Settings
</a>
}
else
{
<a asp-action="SetupLightningNode" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Id" id="@($"Modify-Lightning{scheme.CryptoCode}")" class="btn btn-primary btn-sm ms-4 px-3 py-1 fw-semibold">
Setup
</a>
}
</span>
</div>
</li>
}
</ul>
</div>
<form method="post">
<h3 class="mb-3">Payment</h3>
<div class="form-group">
<label asp-for="DefaultCurrency" class="form-label"></label>
<input asp-for="DefaultCurrency" class="form-control" style="max-width:10ch;" />
<span asp-validation-for="DefaultCurrency" class="text-danger"></span>
</div>
<div class="form-group d-flex align-items-center">
<input asp-for="AnyoneCanCreateInvoice" type="checkbox" class="btcpay-toggle me-2"/>
<label asp-for="AnyoneCanCreateInvoice" class="form-label mb-0 me-1"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
</div>
<div class="form-group mt-4">
<label asp-for="NetworkFeeMode" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#add-network-fee-to-invoice-vary-with-mining-fees" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<select asp-for="NetworkFeeMode" class="form-select">
<option value="MultiplePaymentsOnly">... only if the customer makes more than one payment for the invoice</option>
<option value="Always">... on every payment</option>
<option value="Never">Never add network fee</option>
</select>
</div>
<div class="form-group">
<label asp-for="InvoiceExpiration" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#invoice-expires-if-the-full-amount-has-not-been-paid-after-minutes" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<div class="input-group">
<input asp-for="InvoiceExpiration" class="form-control" style="max-width:10ch;"/>
<span class="input-group-text">minutes</span>
</div>
<span asp-validation-for="InvoiceExpiration" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PaymentTolerance" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-paid-even-if-the-paid-amount-is-less-than-expected" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<div class="input-group">
<input asp-for="PaymentTolerance" class="form-control" style="max-width:10ch;"/>
<span class="input-group-text">percent</span>
</div>
<span asp-validation-for="PaymentTolerance" class="text-danger"></span>
</div>
<button name="command" type="submit" class="btn btn-primary px-4 mt-3" value="Save" id="Save">Save</button>
</form>
</div>
</div>
@section PageFootContent {
<partial name="_ValidationScriptsPartial" />
}

View File

@ -2,6 +2,6 @@ namespace BTCPayServer.Views.Stores
{ {
public enum StoreNavPages public enum StoreNavPages
{ {
Create, Dashboard, Rates, PaymentMethods, OnchainSettings, LightningSettings, Lightning, CheckoutAppearance, General, Tokens, Users, PayButton, Integrations, Webhooks, PullPayments, Payouts Create, Dashboard, Rates, Payment, OnchainSettings, LightningSettings, Lightning, CheckoutAppearance, General, Tokens, Users, PayButton, Integrations, Webhooks, PullPayments, Payouts
} }
} }

View File

@ -61,6 +61,13 @@
</div> </div>
<form method="post" asp-action="UpdateWalletSettings" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode"> <form method="post" asp-action="UpdateWalletSettings" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode">
<div class="form-group">
<div class="d-flex align-items-center">
<input asp-for="Enabled" type="checkbox" class="btcpay-toggle me-2"/>
<label asp-for="Enabled" class="form-label mb-0 me-1"></label>
</div>
<span asp-validation-for="PayJoinEnabled" class="text-danger"></span>
</div>
<div class="form-group"> <div class="form-group">
<label asp-for="Label" class="form-label"></label> <label asp-for="Label" class="form-label"></label>
<input asp-for="Label" class="form-control" style="max-width:24em;" /> <input asp-for="Label" class="form-control" style="max-width:24em;" />

View File

@ -13,7 +13,7 @@
@section Navbar { @section Navbar {
@await RenderSectionAsync("Navbar", false) @await RenderSectionAsync("Navbar", false)
<a asp-controller="UIStores" asp-action="PaymentMethods" asp-route-storeId="@Context.GetRouteValue("storeId")" class="cancel"> <a asp-controller="UIStores" asp-action="Payment" asp-route-storeId="@Context.GetRouteValue("storeId")" class="cancel">
<vc:icon symbol="close" /> <vc:icon symbol="close" />
</a> </a>
} }

View File

@ -1,7 +1,7 @@
@using BTCPayServer.Client @using BTCPayServer.Client
<nav id="SectionNav" class="nav"> <nav id="SectionNav" class="nav">
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.General))" class="nav-link @ViewData.IsActivePage(StoreNavPages.General)" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Context.GetRouteValue("storeId")">General</a> <a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.General))" class="nav-link @ViewData.IsActivePage(StoreNavPages.General)" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Context.GetRouteValue("storeId")">General</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.PaymentMethods))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PaymentMethods)" asp-controller="UIStores" asp-action="PaymentMethods" asp-route-storeId="@Context.GetRouteValue("storeId")">Payment Methods</a> <a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Payment))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payment)" asp-controller="UIStores" asp-action="Payment" asp-route-storeId="@Context.GetRouteValue("storeId")">Payment</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Rates)" asp-controller="UIStores" asp-action="Rates" asp-route-storeId="@Context.GetRouteValue("storeId")">Rates</a> <a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Rates)" asp-controller="UIStores" asp-action="Rates" asp-route-storeId="@Context.GetRouteValue("storeId")">Rates</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.CheckoutAppearance))" class="nav-link @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance)" asp-controller="UIStores" asp-action="CheckoutAppearance" asp-route-storeId="@Context.GetRouteValue("storeId")">Checkout Appearance</a> <a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.CheckoutAppearance))" class="nav-link @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance)" asp-controller="UIStores" asp-action="CheckoutAppearance" asp-route-storeId="@Context.GetRouteValue("storeId")">Checkout Appearance</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Tokens)" asp-controller="UIStores" asp-action="ListTokens" asp-route-storeId="@Context.GetRouteValue("storeId")">Access Tokens</a> <a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Tokens)" asp-controller="UIStores" asp-action="ListTokens" asp-route-storeId="@Context.GetRouteValue("storeId")">Access Tokens</a>

View File

@ -44,7 +44,7 @@
<tr> <tr>
@if (wallet.IsOwner) @if (wallet.IsOwner)
{ {
<td><a asp-action="PaymentMethods" asp-controller="UIStores" asp-route-storeId="@wallet.StoreId">@wallet.StoreName</a></td> <td><a asp-action="Payment" asp-controller="UIStores" asp-route-storeId="@wallet.StoreId">@wallet.StoreName</a></td>
} }
else else
{ {