diff --git a/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs b/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs index 6a6510f17..e131923a2 100644 --- a/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs +++ b/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs @@ -9,6 +9,7 @@ namespace BTCPayServer.Abstractions.Extensions { private const string ACTIVE_CATEGORY_KEY = "ActiveCategory"; private const string ACTIVE_PAGE_KEY = "ActivePage"; + private const string ACTIVE_ID_KEY = "ActiveId"; public static void SetActivePageAndTitle(this ViewDataDictionary viewData, T activePage, string title = null, string mainTitle = null) where T : IConvertible @@ -28,25 +29,38 @@ namespace BTCPayServer.Abstractions.Extensions viewData[ACTIVE_CATEGORY_KEY] = activeCategory; } - public static string IsActiveCategory(this ViewDataDictionary viewData, T category) + // TODO: Refactor this and merge it with SetActivePage + public static void SetActiveId(this ViewDataDictionary viewData, T activeId) + { + viewData[ACTIVE_ID_KEY] = activeId; + } + + public static string IsActiveCategory(this ViewDataDictionary viewData, T category, object id = null) { if (!viewData.ContainsKey(ACTIVE_CATEGORY_KEY)) { return null; } + var activeId = viewData[ACTIVE_ID_KEY]; var activeCategory = (T)viewData[ACTIVE_CATEGORY_KEY]; - return category.Equals(activeCategory) ? "active" : null; + var categoryMatch = category.Equals(activeCategory); + var idMatch = id == null || activeId == null || id.Equals(activeId); + return categoryMatch && idMatch ? "active" : null; } - public static string IsActivePage(this ViewDataDictionary viewData, T page) + public static string IsActivePage(this ViewDataDictionary viewData, T page, object id = null) where T : IConvertible { if (!viewData.ContainsKey(ACTIVE_PAGE_KEY)) { return null; } + var activeId = viewData[ACTIVE_ID_KEY]; var activePage = (T)viewData[ACTIVE_PAGE_KEY]; - return page.Equals(activePage) ? "active" : null; + var activeCategory = viewData[ACTIVE_CATEGORY_KEY]; + var categoryAndPageMatch = activeCategory.Equals(activePage.GetType()) && page.Equals(activePage); + var idMatch = id == null || activeId == null || id.Equals(activeId); + return categoryAndPageMatch && idMatch ? "active" : null; } public static HtmlString ToBrowserDate(this DateTimeOffset date) diff --git a/BTCPayServer.Plugins.Test/Views/TestExtension/Index.cshtml b/BTCPayServer.Plugins.Test/Views/TestExtension/Index.cshtml index a8a9f9ca8..24a7581ad 100644 --- a/BTCPayServer.Plugins.Test/Views/TestExtension/Index.cshtml +++ b/BTCPayServer.Plugins.Test/Views/TestExtension/Index.cshtml @@ -1,21 +1,21 @@ @model BTCPayServer.Plugins.Test.TestPluginPageViewModel -
-
-

Challenge Completed!!

- Here is also an image loaded from the plugin
- - - -
-

Persisted Data

-

The following is data persisted to the configured database but in an isolated DbContext. Every time you start BTCPay Server with this plugin enabled, a timestamp is logged.

-
    - @foreach (var item in Model.Data) - { -
  • @item.Id at @item.Timestamp.ToString("F")
  • - } -
-
+
+

Challenge Completed!!

+ Here is also an image loaded from the plugin
+ + + + +
+

Persisted Data

+

The following is data persisted to the configured database but in an isolated DbContext. Every time you start BTCPay Server with this plugin enabled, a timestamp is logged.

+
    + @foreach (var item in Model.Data) + { +
  • @item.Id at @item.Timestamp.ToString("F")
  • + } +
-
+ + diff --git a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs index 87ac2aa72..3e294f345 100644 --- a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs +++ b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs @@ -607,15 +607,15 @@ namespace BTCPayServer.Tests tester.ActivateLTC(); await tester.StartAsync(); var user = tester.NewAccount(); - user.GrantAccess(); + await user.GrantAccessAsync(); user.RegisterDerivationScheme("BTC"); user.RegisterDerivationScheme("LTC"); var apps = user.GetController(); - var vm = Assert.IsType(Assert.IsType(apps.CreateApp().Result).Model); + var vm = Assert.IsType(Assert.IsType(apps.CreateApp(user.StoreId)).Model); vm.AppName = "test"; vm.SelectedAppType = AppType.PointOfSale.ToString(); - Assert.IsType(apps.CreateApp(vm).Result); - var appId = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model) + Assert.IsType(apps.CreateApp(user.StoreId, vm).Result); + var appId = Assert.IsType(Assert.IsType(apps.ListApps(user.StoreId).Result).Model) .Apps[0].Id; var vmpos = Assert.IsType(Assert .IsType(apps.UpdatePointOfSale(appId).Result).Model); @@ -658,13 +658,11 @@ donation: .ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "orange").Result); // - var invoices = user.BitPay.GetInvoices(); + var invoices = await user.BitPay.GetInvoicesAsync(); var orangeInvoice = invoices.First(); Assert.Equal(10.00m, orangeInvoice.Price); Assert.Equal("CAD", orangeInvoice.Currency); Assert.Equal("orange", orangeInvoice.ItemDesc); - - Assert.IsType(publicApps .ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "apple").Result); @@ -672,8 +670,7 @@ donation: var appleInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("apple")); Assert.NotNull(appleInvoice); Assert.Equal("good apple", appleInvoice.ItemDesc); - - + // testing custom amount var action = Assert.IsType(publicApps .ViewPointOfSale(appId, PosViewType.Cart, 6.6m, null, null, null, null, "donation").Result); @@ -772,7 +769,7 @@ noninventoryitem: Assert.Single(invoices.Where(invoice => invoice.ItemCode.Equals("inventoryitem"))); Assert.NotNull(inventoryItemInvoice); - //let's mark the inventoryitem invoice as invalid, thsi should return the item to back in stock + //let's mark the inventoryitem invoice as invalid, this should return the item to back in stock var controller = tester.PayTester.GetController(user.UserId, user.StoreId); var appService = tester.PayTester.GetService(); var eventAggregator = tester.PayTester.GetService(); @@ -786,9 +783,7 @@ noninventoryitem: appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory); }, 10000); - //test payment methods option - vmpos = Assert.IsType(Assert .IsType(apps.UpdatePointOfSale(appId).Result).Model); vmpos.Title = "hello"; @@ -858,7 +853,6 @@ g: Assert.Contains(items, item => item.Id == "f" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup); Assert.Contains(items, item => item.Id == "g" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup); - Assert.IsType(publicApps .ViewPointOfSale(appId, PosViewType.Static, null, null, null, null, null, "g").Result); invoices = user.BitPay.GetInvoices(); diff --git a/BTCPayServer.Tests/ApiKeysTests.cs b/BTCPayServer.Tests/ApiKeysTests.cs index cbdc969a7..11a9af9e4 100644 --- a/BTCPayServer.Tests/ApiKeysTests.cs +++ b/BTCPayServer.Tests/ApiKeysTests.cs @@ -41,7 +41,7 @@ namespace BTCPayServer.Tests var tester = s.Server; var user = tester.NewAccount(); - user.GrantAccess(); + await user.GrantAccessAsync(); await user.MakeAdmin(false); s.GoToLogin(); s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); diff --git a/BTCPayServer.Tests/CrowdfundTests.cs b/BTCPayServer.Tests/CrowdfundTests.cs index 81af549e9..9ccb3aa9e 100644 --- a/BTCPayServer.Tests/CrowdfundTests.cs +++ b/BTCPayServer.Tests/CrowdfundTests.cs @@ -29,21 +29,21 @@ namespace BTCPayServer.Tests { await tester.StartAsync(); var user = tester.NewAccount(); - user.GrantAccess(); + await user.GrantAccessAsync(); var user2 = tester.NewAccount(); - user2.GrantAccess(); + await user2.GrantAccessAsync(); var apps = user.GetController(); var apps2 = user2.GetController(); - var vm = Assert.IsType(Assert.IsType(apps.CreateApp().Result).Model); + var vm = Assert.IsType(Assert.IsType(apps.CreateApp(user.StoreId)).Model); Assert.NotNull(vm.SelectedAppType); Assert.Null(vm.AppName); vm.AppName = "test"; vm.SelectedAppType = AppType.Crowdfund.ToString(); - var redirectToAction = Assert.IsType(apps.CreateApp(vm).Result); + var redirectToAction = Assert.IsType(apps.CreateApp(user.StoreId, vm).Result); Assert.Equal(nameof(apps.UpdateCrowdfund), redirectToAction.ActionName); - var appList = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model); + var appList = Assert.IsType(Assert.IsType(apps.ListApps(user.StoreId).Result).Model); var appList2 = - Assert.IsType(Assert.IsType(apps2.ListApps().Result).Model); + Assert.IsType(Assert.IsType(apps2.ListApps(user2.StoreId).Result).Model); Assert.Single(appList.Apps); Assert.Empty(appList2.Apps); Assert.Equal("test", appList.Apps[0].AppName); @@ -54,13 +54,11 @@ namespace BTCPayServer.Tests Assert.IsType(apps.DeleteApp(appList.Apps[0].Id).Result); redirectToAction = Assert.IsType(apps.DeleteAppPost(appList.Apps[0].Id).Result); Assert.Equal(nameof(apps.ListApps), redirectToAction.ActionName); - appList = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model); + appList = Assert.IsType(Assert.IsType(apps.ListApps(user.StoreId).Result).Model); Assert.Empty(appList.Apps); } } - - [Fact(Timeout = LongRunningTestTimeout)] [Trait("Integration", "Integration")] public async Task CanContributeOnlyWhenAllowed() @@ -69,14 +67,14 @@ namespace BTCPayServer.Tests { await tester.StartAsync(); var user = tester.NewAccount(); - user.GrantAccess(); + await user.GrantAccessAsync(); user.RegisterDerivationScheme("BTC"); var apps = user.GetController(); - var vm = Assert.IsType(Assert.IsType(apps.CreateApp().Result).Model); + var vm = Assert.IsType(Assert.IsType(apps.CreateApp(user.StoreId)).Model); vm.AppName = "test"; vm.SelectedAppType = AppType.Crowdfund.ToString(); - Assert.IsType(apps.CreateApp(vm).Result); - var appId = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model) + Assert.IsType(apps.CreateApp(user.StoreId, vm).Result); + var appId = Assert.IsType(Assert.IsType(apps.ListApps(user.StoreId).Result).Model) .Apps[0].Id; //Scenario 1: Not Enabled - Not Allowed @@ -90,8 +88,7 @@ namespace BTCPayServer.Tests var anonAppPubsController = tester.PayTester.GetController(); var publicApps = user.GetController(); - - + Assert.IsType(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund() { Amount = new decimal(0.01) @@ -119,7 +116,6 @@ namespace BTCPayServer.Tests }, default)); //Scenario 4: Enabled But End Date < Now - Not Allowed - crowdfundViewModel.StartDate = DateTime.Today.AddDays(-2); crowdfundViewModel.EndDate = DateTime.Today.AddDays(-1); crowdfundViewModel.Enabled = true; @@ -130,7 +126,6 @@ namespace BTCPayServer.Tests Amount = new decimal(0.01) }, default)); - //Scenario 5: Enabled and within correct timeframe, however target is enforced and Amount is Over - Not Allowed crowdfundViewModel.StartDate = DateTime.Today.AddDays(-2); crowdfundViewModel.EndDate = DateTime.Today.AddDays(2); @@ -149,7 +144,6 @@ namespace BTCPayServer.Tests { Amount = new decimal(0.05) }, default)); - } } @@ -165,11 +159,11 @@ namespace BTCPayServer.Tests user.RegisterDerivationScheme("BTC"); await user.SetNetworkFeeMode(NetworkFeeMode.Never); var apps = user.GetController(); - var vm = Assert.IsType(Assert.IsType(apps.CreateApp().Result).Model); + var vm = Assert.IsType(Assert.IsType(apps.CreateApp(user.StoreId)).Model); vm.AppName = "test"; vm.SelectedAppType = AppType.Crowdfund.ToString(); - Assert.IsType(apps.CreateApp(vm).Result); - var appId = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model) + Assert.IsType(apps.CreateApp(user.StoreId, vm).Result); + var appId = Assert.IsType(Assert.IsType(apps.ListApps(user.StoreId).Result).Model) .Apps[0].Id; TestLogs.LogInformation("We create an invoice with a hardcap"); @@ -188,8 +182,7 @@ namespace BTCPayServer.Tests var model = Assert.IsType(Assert .IsType(publicApps.ViewCrowdfund(appId, String.Empty).Result).Model); - - + Assert.Equal(crowdfundViewModel.TargetAmount, model.TargetAmount); Assert.Equal(crowdfundViewModel.EndDate, model.EndDate); Assert.Equal(crowdfundViewModel.StartDate, model.StartDate); @@ -197,11 +190,10 @@ namespace BTCPayServer.Tests Assert.Equal(0m, model.Info.CurrentAmount); Assert.Equal(0m, model.Info.CurrentPendingAmount); Assert.Equal(0m, model.Info.ProgressPercentage); - - + TestLogs.LogInformation("Unpaid invoices should show as pending contribution because it is hardcap"); TestLogs.LogInformation("Because UseAllStoreInvoices is true, we can manually create an invoice and it should show as contribution"); - var invoice = user.BitPay.CreateInvoice(new Invoice() + var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice { Buyer = new Buyer() { email = "test@fwf.com" }, Price = 1m, @@ -211,10 +203,9 @@ namespace BTCPayServer.Tests TransactionSpeed = "high", FullNotifications = true }, Facade.Merchant); - - + model = Assert.IsType(Assert - .IsType(publicApps.ViewCrowdfund(appId, String.Empty).Result).Model); + .IsType(publicApps.ViewCrowdfund(appId, string.Empty).Result).Model); Assert.Equal(0m, model.Info.CurrentAmount); Assert.Equal(1m, model.Info.CurrentPendingAmount); @@ -242,9 +233,9 @@ namespace BTCPayServer.Tests Assert.IsType(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result); TestLogs.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged"); - invoice = user.BitPay.CreateInvoice(new Invoice() + invoice = await user.BitPay.CreateInvoiceAsync(new Invoice { - Buyer = new Buyer() { email = "test@fwf.com" }, + Buyer = new Buyer { email = "test@fwf.com" }, Price = 1m, Currency = "BTC", PosData = "posData", @@ -259,9 +250,9 @@ namespace BTCPayServer.Tests crowdfundViewModel.EnforceTargetAmount = false; crowdfundViewModel.UseAllStoreInvoices = true; Assert.IsType(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result); - invoice = user.BitPay.CreateInvoice(new Invoice() + invoice = await user.BitPay.CreateInvoiceAsync(new Invoice { - Buyer = new Buyer() { email = "test@fwf.com" }, + Buyer = new Buyer { email = "test@fwf.com" }, Price = 1m, Currency = "BTC", PosData = "posData", @@ -271,20 +262,15 @@ namespace BTCPayServer.Tests }, Facade.Merchant); Assert.Equal(0m, model.Info.CurrentPendingAmount); invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network); - tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Coins(0.5m)); - tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Coins(0.2m)); + await tester.ExplorerNode.SendToAddressAsync(invoiceAddress, Money.Coins(0.5m)); + await tester.ExplorerNode.SendToAddressAsync(invoiceAddress, Money.Coins(0.2m)); TestUtils.Eventually(() => { model = Assert.IsType(Assert - .IsType(publicApps.ViewCrowdfund(appId, String.Empty).Result).Model); + .IsType(publicApps.ViewCrowdfund(appId, string.Empty).Result).Model); Assert.Equal(0.7m, model.Info.CurrentPendingAmount); }); } - - } - } - - } diff --git a/BTCPayServer.Tests/Extensions.cs b/BTCPayServer.Tests/Extensions.cs index 3097a5bc3..563c9ea4f 100644 --- a/BTCPayServer.Tests/Extensions.cs +++ b/BTCPayServer.Tests/Extensions.cs @@ -28,7 +28,6 @@ namespace BTCPayServer.Tests public static void AssertNoError(this IWebDriver driver) { - Assert.NotEmpty(driver.FindElements(By.ClassName("navbar-brand"))); if (!driver.PageSource.Contains("alert-danger")) return; foreach (var dangerAlert in driver.FindElements(By.ClassName("alert-danger"))) Assert.False(dangerAlert.Displayed, $"No alert should be displayed, but found this on {driver.Url}: {dangerAlert.Text}"); diff --git a/BTCPayServer.Tests/POSTests.cs b/BTCPayServer.Tests/POSTests.cs index 571f28515..33612f60d 100644 --- a/BTCPayServer.Tests/POSTests.cs +++ b/BTCPayServer.Tests/POSTests.cs @@ -25,14 +25,14 @@ namespace BTCPayServer.Tests { await tester.StartAsync(); var user = tester.NewAccount(); - user.GrantAccess(); + await user.GrantAccessAsync(); user.RegisterDerivationScheme("BTC"); var apps = user.GetController(); - var vm = Assert.IsType(Assert.IsType(apps.CreateApp().Result).Model); + var vm = Assert.IsType(Assert.IsType(apps.CreateApp(user.StoreId)).Model); vm.AppName = "test"; vm.SelectedAppType = AppType.PointOfSale.ToString(); - Assert.IsType(apps.CreateApp(vm).Result); - var appId = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model) + Assert.IsType(apps.CreateApp(user.StoreId, vm).Result); + var appId = Assert.IsType(Assert.IsType(apps.ListApps(user.StoreId).Result).Model) .Apps[0].Id; var vmpos = Assert.IsType(Assert .IsType(apps.UpdatePointOfSale(appId).Result).Model); diff --git a/BTCPayServer.Tests/PaymentRequestTests.cs b/BTCPayServer.Tests/PaymentRequestTests.cs index 06adbb32d..3525828ab 100644 --- a/BTCPayServer.Tests/PaymentRequestTests.cs +++ b/BTCPayServer.Tests/PaymentRequestTests.cs @@ -1,16 +1,11 @@ using System; using System.Linq; using System.Threading.Tasks; -using BTCPayServer.Client.Models; using BTCPayServer.Controllers; using BTCPayServer.Models.PaymentRequestViewModels; -using BTCPayServer.PaymentRequest; using BTCPayServer.Services.Invoices; using BTCPayServer.Services.PaymentRequests; -using BTCPayServer.Tests.Logging; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using NBitcoin; using NBitpayClient; using Xunit; using Xunit.Abstractions; @@ -32,16 +27,17 @@ namespace BTCPayServer.Tests { await tester.StartAsync(); var user = tester.NewAccount(); - user.GrantAccess(); + await user.GrantAccessAsync(); user.RegisterDerivationScheme("BTC"); var user2 = tester.NewAccount(); - user2.GrantAccess(); + + await user2.GrantAccessAsync(); var paymentRequestController = user.GetController(); var guestpaymentRequestController = user2.GetController(); - var request = new UpdatePaymentRequestViewModel() + var request = new UpdatePaymentRequestViewModel { Title = "original juice", Currency = "BTC", @@ -49,14 +45,13 @@ namespace BTCPayServer.Tests StoreId = user.StoreId, Description = "description" }; - var id = (Assert + var id = Assert .IsType(await paymentRequestController.EditPaymentRequest(null, request)) - .RouteValues.Values.First().ToString()); + .RouteValues.Values.Last().ToString(); - - //permission guard for guests editing + // Permission guard for guests editing Assert - .IsType(await guestpaymentRequestController.EditPaymentRequest(id)); + .IsType(await guestpaymentRequestController.EditPaymentRequest(user.StoreId, id)); request.Title = "update"; Assert.IsType(await paymentRequestController.EditPaymentRequest(id, request)); @@ -70,8 +65,7 @@ namespace BTCPayServer.Tests Assert.IsType(Assert .IsType(await paymentRequestController.ViewPaymentRequest(id)).Model); - //Archive - + // Archive Assert .IsType(await paymentRequestController.TogglePaymentRequestArchival(id)); Assert.True(Assert @@ -80,8 +74,9 @@ namespace BTCPayServer.Tests Assert.Empty(Assert .IsType(Assert - .IsType(await paymentRequestController.GetPaymentRequests()).Model).Items); - //unarchive + .IsType(await paymentRequestController.GetPaymentRequests(user.StoreId)).Model).Items); + + // Unarchive Assert .IsType(await paymentRequestController.TogglePaymentRequestArchival(id)); @@ -91,7 +86,7 @@ namespace BTCPayServer.Tests Assert.Single(Assert .IsType(Assert - .IsType(await paymentRequestController.GetPaymentRequests()).Model).Items); + .IsType(await paymentRequestController.GetPaymentRequests(user.StoreId)).Model).Items); } } @@ -103,7 +98,7 @@ namespace BTCPayServer.Tests { await tester.StartAsync(); var user = tester.NewAccount(); - user.GrantAccess(); + await user.GrantAccessAsync(); user.RegisterDerivationScheme("BTC"); var paymentRequestController = user.GetController(); @@ -122,7 +117,7 @@ namespace BTCPayServer.Tests }; var response = Assert .IsType(paymentRequestController.EditPaymentRequest(null, request).Result) - .RouteValues.First(); + .RouteValues.Last(); var invoiceId = Assert .IsType( @@ -153,7 +148,7 @@ namespace BTCPayServer.Tests response = Assert .IsType(paymentRequestController.EditPaymentRequest(null, request).Result) - .RouteValues.First(); + .RouteValues.Last(); Assert .IsType( @@ -187,7 +182,7 @@ namespace BTCPayServer.Tests }; var response = Assert .IsType(paymentRequestController.EditPaymentRequest(null, request).Result) - .RouteValues.First(); + .RouteValues.Last(); var invoiceId = response.Value.ToString(); await paymentRequestController.PayPaymentRequest(invoiceId, false); Assert.IsType(await @@ -197,7 +192,7 @@ namespace BTCPayServer.Tests response = Assert .IsType(paymentRequestController.EditPaymentRequest(null, request).Result) - .RouteValues.First(); + .RouteValues.Last(); var paymentRequestId = response.Value.ToString(); @@ -231,7 +226,7 @@ namespace BTCPayServer.Tests .Value .ToString(); - invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant); + await user.BitPay.GetInvoiceAsync(invoiceId, Facade.Merchant); //a hack to generate invoices for the payment request is to manually create an invoice with an order id that matches: user.BitPay.CreateInvoice(new Invoice(1, "USD") diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs index af6af7beb..6bce78a2b 100644 --- a/BTCPayServer.Tests/SeleniumTester.cs +++ b/BTCPayServer.Tests/SeleniumTester.cs @@ -2,14 +2,10 @@ using System; using System.Globalization; using System.IO; using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Models; using BTCPayServer.Lightning; using BTCPayServer.Lightning.CLightning; -using BTCPayServer.Tests.Logging; using BTCPayServer.Views.Manage; using BTCPayServer.Views.Server; using BTCPayServer.Views.Stores; @@ -146,14 +142,14 @@ namespace BTCPayServer.Tests public (string storeName, string storeId) CreateNewStore(bool keepId = true) { - Driver.WaitForElement(By.Id("Stores")).Click(); - Driver.WaitForElement(By.Id("CreateStore")).Click(); + Driver.WaitForElement(By.Id("StoreSelectorToggle")).Click(); + Driver.WaitForElement(By.Id("StoreSelectorMenuItem-Create")).Click(); var name = "Store" + RandomUtils.GetUInt64(); Driver.WaitForElement(By.Id("Name")).SendKeys(name); Driver.WaitForElement(By.Id("Create")).Click(); - Driver.FindElement(By.Id($"Nav-{StoreNavPages.GeneralSettings.ToString()}")).Click(); + Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.GeneralSettings.ToString()}")).Click(); var storeId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value"); - Driver.FindElement(By.Id($"Nav-{StoreNavPages.PaymentMethods.ToString()}")).Click(); + Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.PaymentMethods.ToString()}")).Click(); if (keepId) StoreId = storeId; return (name, storeId); @@ -279,9 +275,9 @@ namespace BTCPayServer.Tests } public Logging.ILog TestLogs => Server.TestLogs; - public void ClickOnAllSideMenus() + public void ClickOnAllSectionLinks() { - var links = Driver.FindElements(By.CssSelector(".nav .nav-link")).Select(c => c.GetAttribute("href")).ToList(); + var links = Driver.FindElements(By.CssSelector("#SectionNav .nav-link")).Select(c => c.GetAttribute("href")).ToList(); Driver.AssertNoError(); foreach (var l in links) { @@ -315,6 +311,11 @@ namespace BTCPayServer.Tests Assert.Contains("404 - Page not found", Driver.PageSource); } + internal void AssertAccessDenied() + { + Assert.Contains("Access denied(cryptoCode).NBitcoinNetwork; s.GoToStore(storeId); s.AddLightningNode(cryptoCode, LightningConnectionType.CLightning, false); s.GoToLightningSettings(storeId, cryptoCode); s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true); - s.GoToApps(); - s.Driver.FindElement(By.Id("CreateNewApp")).Click(); + s.Driver.FindElement(By.Id("StoreNav-CreateApp")).Click(); s.Driver.FindElement(By.Id("SelectedAppType")).Click(); s.Driver.FindElement(By.CssSelector("option[value='PointOfSale']")).Click(); s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString()); s.Driver.FindElement(By.Id("Create")).Click(); - s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success); + Thread.Sleep(5000); + Assert.Contains("App successfully created", s.FindAlertMessage().Text); s.Driver.FindElement(By.Id("DefaultView")).Click(); s.Driver.FindElement(By.CssSelector("option[value='3']")).Click(); s.Driver.FindElement(By.Id("SaveSettings")).Click(); - s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success); + Assert.Contains("App updated", s.FindAlertMessage().Text); s.Driver.FindElement(By.Id("ViewApp")).Click(); var btns = s.Driver.FindElements(By.ClassName("lnurl")); @@ -1299,7 +1277,6 @@ namespace BTCPayServer.Tests Assert.EndsWith(choice, parsed.ToString()); Assert.IsType(await LNURL.LNURL.FetchInformation(parsed, new HttpClient())); } - } [Fact] @@ -1611,7 +1588,7 @@ retry: private static void CanSetupEmailCore(SeleniumTester s) { s.Driver.FindElement(By.Id("QuickFillDropdownToggle")).Click(); - s.Driver.FindElement(By.ClassName("dropdown-item")).Click(); + s.Driver.FindElement(By.CssSelector("#quick-fill .dropdown-menu .dropdown-item:first-child")).Click(); s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test@gmail.com"); s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit(); diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 501da1464..388b6bdfd 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -959,7 +959,7 @@ namespace BTCPayServer.Tests private void AssertSearchInvoice(TestAccount acc, bool expected, string invoiceId, string filter) { var result = - (Models.InvoicingModels.InvoicesModel)((ViewResult)acc.GetController() + (InvoicesModel)((ViewResult)acc.GetController() .ListInvoices(new InvoicesModel { SearchTerm = filter }).Result).Model; Assert.Equal(expected, result.Invoices.Any(i => i.InvoiceId == invoiceId)); } @@ -2016,8 +2016,7 @@ namespace BTCPayServer.Tests }); } } - - + [Fact(Timeout = LongRunningTestTimeout)] [Trait("Integration", "Integration")] public async Task CanCreateAndDeleteApps() @@ -2026,21 +2025,21 @@ namespace BTCPayServer.Tests { await tester.StartAsync(); var user = tester.NewAccount(); - user.GrantAccess(); + await user.GrantAccessAsync(); var user2 = tester.NewAccount(); - user2.GrantAccess(); + await user2.GrantAccessAsync(); var apps = user.GetController(); var apps2 = user2.GetController(); - var vm = Assert.IsType(Assert.IsType(apps.CreateApp().Result).Model); + var vm = Assert.IsType(Assert.IsType(apps.CreateApp(user.StoreId)).Model); Assert.NotNull(vm.SelectedAppType); Assert.Null(vm.AppName); vm.AppName = "test"; vm.SelectedAppType = AppType.PointOfSale.ToString(); - var redirectToAction = Assert.IsType(apps.CreateApp(vm).Result); + var redirectToAction = Assert.IsType(apps.CreateApp(user.StoreId, vm).Result); Assert.Equal(nameof(apps.UpdatePointOfSale), redirectToAction.ActionName); - var appList = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model); + var appList = Assert.IsType(Assert.IsType(apps.ListApps(user.StoreId).Result).Model); var appList2 = - Assert.IsType(Assert.IsType(apps2.ListApps().Result).Model); + Assert.IsType(Assert.IsType(apps2.ListApps(user2.StoreId).Result).Model); Assert.Single(appList.Apps); Assert.Empty(appList2.Apps); Assert.Equal("test", appList.Apps[0].AppName); @@ -2051,7 +2050,7 @@ namespace BTCPayServer.Tests Assert.IsType(apps.DeleteApp(appList.Apps[0].Id).Result); redirectToAction = Assert.IsType(apps.DeleteAppPost(appList.Apps[0].Id).Result); Assert.Equal(nameof(apps.ListApps), redirectToAction.ActionName); - appList = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model); + appList = Assert.IsType(Assert.IsType(apps.ListApps(user.StoreId).Result).Model); Assert.Empty(appList.Apps); } } diff --git a/BTCPayServer/Components/MainNav/Default.cshtml b/BTCPayServer/Components/MainNav/Default.cshtml new file mode 100644 index 000000000..b4f8446b3 --- /dev/null +++ b/BTCPayServer/Components/MainNav/Default.cshtml @@ -0,0 +1,285 @@ +@using BTCPayServer.Views.Server +@using BTCPayServer.Views.Stores +@using BTCPayServer.Views.Apps +@using BTCPayServer.Views.Invoice +@using BTCPayServer.Views.Manage +@using BTCPayServer.Views.PaymentRequest +@using BTCPayServer.Views.Wallets +@using BTCPayServer.Abstractions.Extensions +@using BTCPayServer.Abstractions.Contracts +@inject BTCPayServer.Services.BTCPayServerEnvironment Env +@inject SignInManager SignInManager +@inject ISettingsRepository SettingsRepository + +@model BTCPayServer.Components.MainNav.MainNavViewModel +@addTagHelper *, BundlerMinifier.TagHelpers + +@{ + var theme = await SettingsRepository.GetTheme(); +} + + diff --git a/BTCPayServer/Components/MainNav/MainNav.cs b/BTCPayServer/Components/MainNav/MainNav.cs new file mode 100644 index 000000000..bac91064a --- /dev/null +++ b/BTCPayServer/Components/MainNav/MainNav.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BTCPayServer.Controllers; +using BTCPayServer.Data; +using BTCPayServer.Models.StoreViewModels; +using BTCPayServer.Payments; +using BTCPayServer.Payments.Lightning; +using BTCPayServer.Services.Apps; +using BTCPayServer.Services.Invoices; +using BTCPayServer.Services.Stores; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using NBitcoin; +using NBitcoin.Secp256k1; + +namespace BTCPayServer.Components.MainNav +{ + public class MainNav : ViewComponent + { + private const string RootName = "Global"; + private readonly AppService _appService; + private readonly StoreRepository _storeRepo; + private readonly StoresController _storesController; + private readonly BTCPayNetworkProvider _networkProvider; + private readonly UserManager _userManager; + private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary; + + public MainNav( + AppService appService, + StoreRepository storeRepo, + StoresController storesController, + BTCPayNetworkProvider networkProvider, + UserManager userManager, + PaymentMethodHandlerDictionary paymentMethodHandlerDictionary) + { + _storeRepo = storeRepo; + _appService = appService; + _userManager = userManager; + _networkProvider = networkProvider; + _storesController = storesController; + _paymentMethodHandlerDictionary = paymentMethodHandlerDictionary; + } + + public async Task InvokeAsync() + { + var store = ViewContext.HttpContext.GetStoreData(); + var vm = new MainNavViewModel { Store = store }; +#if ALTCOINS + vm.AltcoinsBuild = true; +#endif + if (store != null) + { + var storeBlob = store.GetStoreBlob(); + + // Wallets + _storesController.AddPaymentMethods(store, storeBlob, + out var derivationSchemes, out var lightningNodes); + vm.DerivationSchemes = derivationSchemes; + vm.LightningNodes = lightningNodes; + + // Apps + var apps = await _appService.GetAllApps(UserId, false, store.Id); + vm.Apps = apps.Select(a => new StoreApp + { + Id = a.Id, + AppName = a.AppName, + AppType = a.AppType, + IsOwner = a.IsOwner + }).ToList(); + } + + return View(vm); + } + + private string UserId => _userManager.GetUserId(HttpContext.User); + } +} diff --git a/BTCPayServer/Components/MainNav/MainNavViewModel.cs b/BTCPayServer/Components/MainNav/MainNavViewModel.cs new file mode 100644 index 000000000..4f777095a --- /dev/null +++ b/BTCPayServer/Components/MainNav/MainNavViewModel.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using BTCPayServer.Data; +using BTCPayServer.Models.StoreViewModels; + +namespace BTCPayServer.Components.MainNav +{ + public class MainNavViewModel + { + public StoreData Store { get; set; } + public List DerivationSchemes { get; set; } + public List LightningNodes { get; set; } + public List Apps { get; set; } + public bool AltcoinsBuild { get; set; } + } + + public class StoreApp + { + public string Id { get; set; } + public string AppName { get; set; } + public string AppType { get; set; } + public string Action { get => $"Update{AppType}"; } + public bool IsOwner { get; set; } + } +} diff --git a/BTCPayServer/Components/NotificationsDropdown/Default.cshtml b/BTCPayServer/Components/NotificationsDropdown/Default.cshtml index 4336d49bc..fd22c483a 100644 --- a/BTCPayServer/Components/NotificationsDropdown/Default.cshtml +++ b/BTCPayServer/Components/NotificationsDropdown/Default.cshtml @@ -1,23 +1,19 @@ @inject UserManager UserManager @inject ISettingsRepository SettingsRepository -@using BTCPayServer.HostedServices @using BTCPayServer.Views.Notifications @using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Contracts -@using BTCPayServer.Client -@using BTCPayServer.Services @model BTCPayServer.Components.NotificationsDropdown.NotificationSummaryViewModel @addTagHelper *, BundlerMinifier.TagHelpers -@if (Model.UnseenCount > 0) -{ - -} -else -{ - -} + } + diff --git a/BTCPayServer/Components/NotificationsDropdown/NotificationSummaryViewModel.cs b/BTCPayServer/Components/NotificationsDropdown/NotificationSummaryViewModel.cs index e692f313f..8c9b89937 100644 --- a/BTCPayServer/Components/NotificationsDropdown/NotificationSummaryViewModel.cs +++ b/BTCPayServer/Components/NotificationsDropdown/NotificationSummaryViewModel.cs @@ -1,9 +1,5 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using BTCPayServer.Abstractions.Contracts; -using BTCPayServer.Models.NotificationViewModels; namespace BTCPayServer.Components.NotificationsDropdown { diff --git a/BTCPayServer/Components/StoreSelector/Default.cshtml b/BTCPayServer/Components/StoreSelector/Default.cshtml new file mode 100644 index 000000000..b7df8858b --- /dev/null +++ b/BTCPayServer/Components/StoreSelector/Default.cshtml @@ -0,0 +1,20 @@ +@model BTCPayServer.Components.StoreSelector.StoreSelectorViewModel +@addTagHelper *, BundlerMinifier.TagHelpers + +
+ + +
diff --git a/BTCPayServer/Components/StoreSelector/StoreSelector.cs b/BTCPayServer/Components/StoreSelector/StoreSelector.cs new file mode 100644 index 000000000..902c6237b --- /dev/null +++ b/BTCPayServer/Components/StoreSelector/StoreSelector.cs @@ -0,0 +1,48 @@ +using System.Linq; +using System.Threading.Tasks; +using BTCPayServer.Data; +using BTCPayServer.Services.Stores; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using NBitcoin.Secp256k1; + +namespace BTCPayServer.Components.StoreSelector +{ + public class StoreSelector : ViewComponent + { + private const string RootName = "Global"; + private readonly StoreRepository _storeRepo; + private readonly UserManager _userManager; + + public StoreSelector(StoreRepository storeRepo, UserManager userManager) + { + _storeRepo = storeRepo; + _userManager = userManager; + } + + public async Task InvokeAsync() + { + var userId = _userManager.GetUserId(UserClaimsPrincipal); + var stores = await _storeRepo.GetStoresByUserId(userId); + var currentStore = ViewContext.HttpContext.GetStoreData(); + var options = stores + .Select(store => new SelectListItem + { + Text = store.StoreName, + Value = store.Id, + Selected = store.Id == currentStore?.Id + }) + .ToList(); + + var vm = new StoreSelectorViewModel + { + Options = options, + CurrentStoreId = currentStore?.Id, + CurrentDisplayName = currentStore?.StoreName ?? RootName + }; + + return View(vm); + } + } +} diff --git a/BTCPayServer/Components/StoreSelector/StoreSelectorViewModel.cs b/BTCPayServer/Components/StoreSelector/StoreSelectorViewModel.cs new file mode 100644 index 000000000..21ca705e7 --- /dev/null +++ b/BTCPayServer/Components/StoreSelector/StoreSelectorViewModel.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace BTCPayServer.Components.StoreSelector +{ + public class StoreSelectorViewModel + { + public List Options { get; set; } + public string CurrentStoreId { get; set; } + public string CurrentDisplayName { get; set; } + } +} diff --git a/BTCPayServer/Components/ThemeSwitch/Default.cshtml b/BTCPayServer/Components/ThemeSwitch/Default.cshtml index dbfb27104..d8364ebc1 100644 --- a/BTCPayServer/Components/ThemeSwitch/Default.cshtml +++ b/BTCPayServer/Components/ThemeSwitch/Default.cshtml @@ -1,12 +1,9 @@ @model BTCPayServer.Components.ThemeSwitch.ThemeSwitchViewModel diff --git a/BTCPayServer/Components/ThemeSwitch/ThemeSwitch.cs b/BTCPayServer/Components/ThemeSwitch/ThemeSwitch.cs index cf8f41ae9..b0018256b 100644 --- a/BTCPayServer/Components/ThemeSwitch/ThemeSwitch.cs +++ b/BTCPayServer/Components/ThemeSwitch/ThemeSwitch.cs @@ -4,12 +4,11 @@ namespace BTCPayServer.Components.ThemeSwitch { public class ThemeSwitch : ViewComponent { - public IViewComponentResult Invoke(string cssClass = null, string responsive = null) + public IViewComponentResult Invoke(string cssClass = null) { var vm = new ThemeSwitchViewModel { CssClass = cssClass, - Responsive = responsive }; return View(vm); } diff --git a/BTCPayServer/Components/ThemeSwitch/ThemeSwitchViewModel.cs b/BTCPayServer/Components/ThemeSwitch/ThemeSwitchViewModel.cs index 6b4652e1b..f6a1dd9b1 100644 --- a/BTCPayServer/Components/ThemeSwitch/ThemeSwitchViewModel.cs +++ b/BTCPayServer/Components/ThemeSwitch/ThemeSwitchViewModel.cs @@ -3,6 +3,5 @@ namespace BTCPayServer.Components.ThemeSwitch public class ThemeSwitchViewModel { public string CssClass { get; set; } - public string Responsive { get; set; } } } diff --git a/BTCPayServer/Controllers/AppsController.Crowdfund.cs b/BTCPayServer/Controllers/AppsController.Crowdfund.cs index 8ed9f1717..fab200cc9 100644 --- a/BTCPayServer/Controllers/AppsController.Crowdfund.cs +++ b/BTCPayServer/Controllers/AppsController.Crowdfund.cs @@ -1,9 +1,11 @@ using System; -using BTCPayServer.Data; using System.Linq; using System.Threading.Tasks; +using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Client; using BTCPayServer.Models.AppViewModels; using BTCPayServer.Services.Apps; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace BTCPayServer.Controllers @@ -21,6 +23,7 @@ namespace BTCPayServer.Controllers } } + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpGet("{appId}/settings/crowdfund")] public async Task UpdateCrowdfund(string appId) { @@ -64,8 +67,8 @@ namespace BTCPayServer.Controllers }; return View(vm); } - [HttpPost] - [Route("{appId}/settings/crowdfund")] + + [HttpPost("{appId}/settings/crowdfund")] public async Task UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm, string command) { var app = await GetOwnedApp(appId, AppType.Crowdfund); @@ -77,7 +80,7 @@ namespace BTCPayServer.Controllers try { - vm.PerksTemplate = _AppService.SerializeTemplate(_AppService.Parse(vm.PerksTemplate, vm.TargetCurrency)); + vm.PerksTemplate = _appService.SerializeTemplate(_appService.Parse(vm.PerksTemplate, vm.TargetCurrency)); } catch { @@ -155,9 +158,9 @@ namespace BTCPayServer.Controllers app.TagAllInvoices = vm.UseAllStoreInvoices; app.SetSettings(newSettings); - await _AppService.UpdateOrCreateApp(app); + await _appService.UpdateOrCreateApp(app); - _EventAggregator.Publish(new AppUpdated() + _eventAggregator.Publish(new AppUpdated() { AppId = appId, StoreId = app.StoreDataId, diff --git a/BTCPayServer/Controllers/AppsController.PointOfSale.cs b/BTCPayServer/Controllers/AppsController.PointOfSale.cs index 24fc30de0..f06abd48f 100644 --- a/BTCPayServer/Controllers/AppsController.PointOfSale.cs +++ b/BTCPayServer/Controllers/AppsController.PointOfSale.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using BTCPayServer.Data; using System.Text; using System.Text.Encodings.Web; using System.Text.RegularExpressions; @@ -88,13 +87,13 @@ namespace BTCPayServer.Controllers public bool? RedirectAutomatically { get; set; } } - [HttpGet] - [Route("{appId}/settings/pos")] + [HttpGet("{appId}/settings/pos")] public async Task UpdatePointOfSale(string appId) { var app = await GetOwnedApp(appId, AppType.PointOfSale); if (app == null) return NotFound(); + var settings = app.GetSettings(); settings.DefaultView = settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView; settings.EnableShoppingCart = false; @@ -143,8 +142,7 @@ namespace BTCPayServer.Controllers } try { - - var items = _AppService.Parse(settings.Template, settings.Currency); + var items = _appService.Parse(settings.Template, settings.Currency); var builder = new StringBuilder(); builder.AppendLine($""); builder.AppendLine($" "); @@ -162,10 +160,10 @@ namespace BTCPayServer.Controllers vm.ExampleCallback = "{\n \"id\":\"SkdsDghkdP3D3qkj7bLq3\",\n \"url\":\"https://btcpay.example.com/invoice?id=SkdsDghkdP3D3qkj7bLq3\",\n \"status\":\"paid\",\n \"price\":10,\n \"currency\":\"EUR\",\n \"invoiceTime\":1520373130312,\n \"expirationTime\":1520374030312,\n \"currentTime\":1520373179327,\n \"exceptionStatus\":false,\n \"buyerFields\":{\n \"buyerEmail\":\"customer@example.com\",\n \"buyerNotify\":false\n },\n \"paymentSubtotals\": {\n \"BTC\":114700\n },\n \"paymentTotals\": {\n \"BTC\":118400\n },\n \"transactionCurrency\": \"BTC\",\n \"amountPaid\": \"1025900\",\n \"exchangeRates\": {\n \"BTC\": {\n \"EUR\": 8721.690715789999,\n \"USD\": 10817.99\n }\n }\n}"; return View(vm); } - [HttpPost] - [Route("{appId}/settings/pos")] + + [HttpPost("{appId}/settings/pos")] public async Task UpdatePointOfSale(string appId, UpdatePointOfSaleViewModel vm) - { + { var app = await GetOwnedApp(appId, AppType.PointOfSale); if (app == null) return NotFound(); @@ -178,7 +176,7 @@ namespace BTCPayServer.Controllers ModelState.AddModelError(nameof(vm.Currency), "Invalid currency"); try { - vm.Template = _AppService.SerializeTemplate(_AppService.Parse(vm.Template, vm.Currency)); + vm.Template = _appService.SerializeTemplate(_appService.Parse(vm.Template, vm.Currency)); } catch { @@ -211,12 +209,11 @@ namespace BTCPayServer.Controllers RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? (bool?)null : bool.Parse(vm.RedirectAutomatically), RequiresRefundEmail = vm.RequiresRefundEmail, }); - await _AppService.UpdateOrCreateApp(app); + await _appService.UpdateOrCreateApp(app); TempData[WellKnownTempData.SuccessMessage] = "App updated"; return RedirectToAction(nameof(UpdatePointOfSale), new { appId }); } - - + private int[] ListSplit(string list, string separator = ",") { if (string.IsNullOrEmpty(list)) @@ -229,7 +226,7 @@ namespace BTCPayServer.Controllers Regex charsToDestroy = new Regex(@"[^\d|\" + separator + "]"); list = charsToDestroy.Replace(list, ""); - return list.Split(separator, System.StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); + return list.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); } } } diff --git a/BTCPayServer/Controllers/AppsController.cs b/BTCPayServer/Controllers/AppsController.cs index 0d8e649d7..0e7e7c6af 100644 --- a/BTCPayServer/Controllers/AppsController.cs +++ b/BTCPayServer/Controllers/AppsController.cs @@ -3,13 +3,10 @@ using BTCPayServer.Data; using System.Linq; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; -using BTCPayServer.Abstractions.Extensions; -using BTCPayServer.Abstractions.Models; +using BTCPayServer.Client; using BTCPayServer.Models; using BTCPayServer.Models.AppViewModels; -using BTCPayServer.Security; using BTCPayServer.Services.Apps; -using BTCPayServer.Services.Mails; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; @@ -18,65 +15,58 @@ using Microsoft.AspNetCore.Mvc; namespace BTCPayServer.Controllers { - [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [AutoValidateAntiforgeryToken] [Route("apps")] public partial class AppsController : Controller { public AppsController( UserManager userManager, - ApplicationDbContextFactory contextFactory, EventAggregator eventAggregator, - BTCPayNetworkProvider networkProvider, CurrencyNameTable currencies, - EmailSenderFactory emailSenderFactory, - Services.Stores.StoreRepository storeRepository, - AppService AppService) + StoreRepository storeRepository, + AppService appService) { - _UserManager = userManager; - _ContextFactory = contextFactory; - _EventAggregator = eventAggregator; - _NetworkProvider = networkProvider; + _userManager = userManager; + _eventAggregator = eventAggregator; _currencies = currencies; - _emailSenderFactory = emailSenderFactory; _storeRepository = storeRepository; - _AppService = AppService; + _appService = appService; } - private readonly UserManager _UserManager; - private readonly ApplicationDbContextFactory _ContextFactory; - private readonly EventAggregator _EventAggregator; - private readonly BTCPayNetworkProvider _NetworkProvider; + private readonly UserManager _userManager; + private readonly EventAggregator _eventAggregator; private readonly CurrencyNameTable _currencies; - private readonly EmailSenderFactory _emailSenderFactory; private readonly StoreRepository _storeRepository; - private readonly AppService _AppService; + private readonly AppService _appService; public string CreatedAppId { get; set; } - + + [HttpGet("/stores/{storeId}/apps")] public async Task ListApps( + string storeId, string sortOrder = null, string sortOrderColumn = null ) { - var apps = await _AppService.GetAllApps(GetUserId()); + var apps = await _appService.GetAllApps(GetUserId(), false, CurrentStore.Id); if (sortOrder != null && sortOrderColumn != null) { apps = apps.OrderByDescending(app => + { + switch (sortOrderColumn) { - switch (sortOrderColumn) - { - case nameof(app.AppName): - return app.AppName; - case nameof(app.StoreName): - return app.StoreName; - case nameof(app.AppType): - return app.AppType; - default: - return app.Id; - } - }).ToArray(); + case nameof(app.AppName): + return app.AppName; + case nameof(app.StoreName): + return app.StoreName; + case nameof(app.AppType): + return app.AppType; + default: + return app.Id; + } + }).ToArray(); switch (sortOrder) { @@ -90,64 +80,27 @@ namespace BTCPayServer.Controllers } } - return View(new ListAppsViewModel() + return View(new ListAppsViewModel { Apps = apps }); } - [HttpPost] - [Route("{appId}/delete")] - public async Task DeleteAppPost(string appId) + [HttpGet("/stores/{storeId}/apps/create")] + public IActionResult CreateApp(string storeId) { - var appData = await GetOwnedApp(appId); - if (appData == null) - return NotFound(); - if (await _AppService.DeleteApp(appData)) - TempData[WellKnownTempData.SuccessMessage] = "App deleted successfully."; - return RedirectToAction(nameof(ListApps)); + return View(new CreateAppViewModel + { + StoreId = CurrentStore.Id + }); } - [HttpGet] - [Route("create")] - public async Task CreateApp() + [HttpPost("/stores/{storeId}/apps/create")] + public async Task CreateApp(string storeId, CreateAppViewModel vm) { - var stores = await _AppService.GetOwnedStores(GetUserId()); - if (stores.Length == 0) - { - TempData.SetStatusMessageModel(new StatusMessageModel() - { - Html = - $"Error: You need to create at least one store. Create store", - Severity = StatusMessageModel.StatusSeverity.Error - }); - return RedirectToAction(nameof(ListApps)); - } - var vm = new CreateAppViewModel(); - vm.SetStores(stores); - return View(vm); - } + vm.StoreId = CurrentStore.Id; - [HttpPost] - [Route("create")] - public async Task CreateApp(CreateAppViewModel vm) - { - var stores = await _AppService.GetOwnedStores(GetUserId()); - if (stores.Length == 0) - { - TempData.SetStatusMessageModel(new StatusMessageModel() - { - Html = - $"Error: You need to create at least one store. Create store", - Severity = StatusMessageModel.StatusSeverity.Error - }); - return RedirectToAction(nameof(ListApps)); - } - var selectedStore = vm.SelectedStore; - vm.SetStores(stores); - vm.SelectedStore = selectedStore; - - if (!Enum.TryParse(vm.SelectedAppType, out AppType appType)) + if (!Enum.TryParse(vm.SelectedAppType, out AppType appType)) ModelState.AddModelError(nameof(vm.SelectedAppType), "Invalid App Type"); if (!ModelState.IsValid) @@ -155,14 +108,9 @@ namespace BTCPayServer.Controllers return View(vm); } - if (!stores.Any(s => s.Id == selectedStore)) - { - TempData[WellKnownTempData.ErrorMessage] = "You are not owner of this store"; - return RedirectToAction(nameof(ListApps)); - } var appData = new AppData { - StoreDataId = selectedStore, + StoreDataId = CurrentStore.Id, Name = vm.AppName, AppType = appType.ToString() }; @@ -171,18 +119,16 @@ namespace BTCPayServer.Controllers switch (appType) { case AppType.Crowdfund: - var emptyCrowdfund = new CrowdfundSettings(); - emptyCrowdfund.TargetCurrency = defaultCurrency; + var emptyCrowdfund = new CrowdfundSettings { TargetCurrency = defaultCurrency }; appData.SetSettings(emptyCrowdfund); break; case AppType.PointOfSale: - var empty = new PointOfSaleSettings(); - empty.Currency = defaultCurrency; + var empty = new PointOfSaleSettings { Currency = defaultCurrency }; appData.SetSettings(empty); break; } - await _AppService.UpdateOrCreateApp(appData); + await _appService.UpdateOrCreateApp(appData); TempData[WellKnownTempData.SuccessMessage] = "App successfully created"; CreatedAppId = appData.Id; @@ -193,19 +139,10 @@ namespace BTCPayServer.Controllers case AppType.Crowdfund: return RedirectToAction(nameof(UpdateCrowdfund), new { appId = appData.Id }); default: - return RedirectToAction(nameof(ListApps)); + return RedirectToAction(nameof(ListApps), new { storeId = appData.StoreDataId }); } } - async Task GetStoreDefaultCurrentIfEmpty(string storeId, string currency) - { - if (String.IsNullOrWhiteSpace(currency)) - { - currency = (await _storeRepository.FindStore(storeId)).GetStoreBlob().DefaultCurrency; - } - return currency.Trim().ToUpperInvariant(); - } - [HttpGet("{appId}/delete")] public async Task DeleteApp(string appId) { @@ -215,14 +152,39 @@ namespace BTCPayServer.Controllers return View("Confirm", new ConfirmModel("Delete app", $"The app {appData.Name} and its settings will be permanently deleted. Are you sure?", "Delete")); } + [HttpPost("{appId}/delete")] + public async Task DeleteAppPost(string appId) + { + var appData = await GetOwnedApp(appId); + if (appData == null) + return NotFound(); + if (await _appService.DeleteApp(appData)) + TempData[WellKnownTempData.SuccessMessage] = "App deleted successfully."; + return RedirectToAction(nameof(ListApps), new { storeId = appData.StoreDataId }); + } + + async Task GetStoreDefaultCurrentIfEmpty(string storeId, string currency) + { + if (string.IsNullOrWhiteSpace(currency)) + { + currency = (await _storeRepository.FindStore(storeId)).GetStoreBlob().DefaultCurrency; + } + return currency.Trim().ToUpperInvariant(); + } + private Task GetOwnedApp(string appId, AppType? type = null) { - return _AppService.GetAppDataIfOwner(GetUserId(), appId, type); + return _appService.GetAppDataIfOwner(GetUserId(), appId, type); } private string GetUserId() { - return _UserManager.GetUserId(User); + return _userManager.GetUserId(User); + } + + private StoreData CurrentStore + { + get => HttpContext.GetStoreData(); } } } diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index baa5cd02e..e8e57f488 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -81,7 +81,7 @@ namespace BTCPayServer.Controllers } [HttpGet("invoices/{invoiceId}")] - [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Invoice(string invoiceId) { var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery() @@ -97,12 +97,12 @@ namespace BTCPayServer.Controllers var store = await _StoreRepository.FindStore(invoice.StoreId); var invoiceState = invoice.GetInvoiceState(); - var model = new InvoiceDetailsModel() + var model = new InvoiceDetailsModel { StoreId = store.Id, StoreName = store.StoreName, StoreLink = Url.Action(nameof(StoresController.PaymentMethods), "Stores", new { storeId = store.Id }), - PaymentRequestLink = Url.Action(nameof(PaymentRequestController.ViewPaymentRequest), "PaymentRequest", new { id = invoice.Metadata.PaymentRequestId }), + PaymentRequestLink = Url.Action(nameof(PaymentRequestController.ViewPaymentRequest), "PaymentRequest", new { payReqId = invoice.Metadata.PaymentRequestId }), Id = invoice.Id, State = invoiceState.Status.ToModernStatus().ToString(), TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" : @@ -141,7 +141,7 @@ namespace BTCPayServer.Controllers var details = InvoicePopulatePayments(invoice); model.CryptoPayments = details.CryptoPayments; model.Payments = details.Payments; - + return View(model); } @@ -183,6 +183,8 @@ namespace BTCPayServer.Controllers new { pullPaymentId = ppId }); } + HttpContext.SetStoreData(invoice.StoreData); + var paymentMethods = invoice.GetBlob(_NetworkProvider).GetPaymentMethods(); var pmis = paymentMethods.Select(method => method.GetId()).ToList(); var options = (await payoutHandlers.GetSupportedPaymentMethods(invoice.StoreData)).Where(id => pmis.Contains(id)).ToList(); @@ -216,18 +218,21 @@ namespace BTCPayServer.Controllers } [HttpPost("invoices/{invoiceId}/refund")] - [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Refund(string invoiceId, RefundModel model, CancellationToken cancellationToken) { using var ctx = _dbContextFactory.CreateContext(); var invoice = await _InvoiceRepository.GetInvoice(invoiceId); if (invoice is null) return NotFound(); + var store = await _StoreRepository.FindStore(invoice.StoreId, GetUserId()); if (store is null) return NotFound(); + if (!CanRefund(invoice.GetInvoiceState())) return NotFound(); + var paymentMethodId = PaymentMethodId.Parse(model.SelectedPaymentMethod); var cdCurrency = _CurrencyNameTable.GetCurrencyData(invoice.Currency, true); var paymentMethodDivisibility = _CurrencyNameTable.GetCurrencyData(paymentMethodId.CryptoCode, false)?.Divisibility ?? 8; @@ -421,7 +426,7 @@ namespace BTCPayServer.Controllers } [HttpPost] - public async Task MassAction(string command, string[] selectedItems) + public async Task MassAction(string command, string[] selectedItems, string? storeId = null) { if (selectedItems != null) { @@ -435,7 +440,7 @@ namespace BTCPayServer.Controllers } } - return RedirectToAction(nameof(ListInvoices)); + return RedirectToAction(nameof(ListInvoices), new { storeId }); } [HttpGet("i/{invoiceId}")] @@ -732,17 +737,29 @@ namespace BTCPayServer.Controllers return Ok("{}"); } + [HttpGet("/stores/{storeId}/invoices")] [HttpGet("invoices")] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [BitpayAPIConstraint(false)] - public async Task ListInvoices(InvoicesModel? model = null) + public async Task ListInvoices(InvoicesModel? model = null, string? storeId = null) { model = this.ParseListQuery(model ?? new InvoicesModel()); var fs = new SearchString(model.SearchTerm); - var storeIds = fs.GetFilterArray("storeid") != null ? fs.GetFilterArray("storeid") : new List().ToArray(); + var storeIds = storeId == null + ? fs.GetFilterArray("storeid") != null ? fs.GetFilterArray("storeid") : new List().ToArray() + : new []{ storeId }; model.StoreIds = storeIds; + + if (storeId != null) + { + var store = await _StoreRepository.FindStore(storeId, GetUserId()); + if (store == null) + return NotFound(); + HttpContext.SetStoreData(store); + model.StoreId = store.Id; + } InvoiceQuery invoiceQuery = GetInvoiceQuery(model.SearchTerm, model.TimezoneOffset ?? 0); var counting = _InvoiceRepository.GetInvoicesTotal(invoiceQuery); @@ -823,6 +840,7 @@ namespace BTCPayServer.Controllers nameof(SelectListItem.Text)); } + [HttpGet("/stores/{storeId}/invoices/create")] [HttpGet("invoices/create")] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [BitpayAPIConstraint(false)] @@ -839,16 +857,26 @@ namespace BTCPayServer.Controllers TempData[WellKnownTempData.ErrorMessage] = "You need to create at least one store before creating a transaction"; return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores"); } + + if (model?.StoreId != null) + { + var store = await _StoreRepository.FindStore(model.StoreId, GetUserId()); + if (store == null) + return NotFound(); + HttpContext.SetStoreData(store); + } var vm = new CreateInvoiceModel { Stores = stores, + StoreId = model?.StoreId, AvailablePaymentMethods = GetPaymentMethodsSelectList() }; return View(vm); } + [HttpPost("/stores/{storeId}/invoices/create")] [HttpPost("invoices/create")] [Authorize(Policy = Policies.CanCreateInvoice, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [BitpayAPIConstraint(false)] @@ -901,7 +929,7 @@ namespace BTCPayServer.Controllers TempData[WellKnownTempData.SuccessMessage] = $"Invoice {result.Data.Id} just created!"; CreatedInvoiceId = result.Data.Id; - return RedirectToAction(nameof(ListInvoices)); + return RedirectToAction(nameof(ListInvoices), new { result.Data.StoreId }); } catch (BitpayHttpException ex) { diff --git a/BTCPayServer/Controllers/PaymentRequestController.cs b/BTCPayServer/Controllers/PaymentRequestController.cs index c5cfa721a..2a16c223d 100644 --- a/BTCPayServer/Controllers/PaymentRequestController.cs +++ b/BTCPayServer/Controllers/PaymentRequestController.cs @@ -4,8 +4,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; -using BTCPayServer.Abstractions.Extensions; -using BTCPayServer.Abstractions.Models; +using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.Filters; @@ -18,15 +17,14 @@ using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Routing; using PaymentRequestData = BTCPayServer.Data.PaymentRequestData; using StoreData = BTCPayServer.Data.StoreData; namespace BTCPayServer.Controllers { + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Route("payment-requests")] - [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] public class PaymentRequestController : Controller { private readonly InvoiceController _InvoiceController; @@ -61,16 +59,17 @@ namespace BTCPayServer.Controllers _linkGenerator = linkGenerator; } - [HttpGet("")] [BitpayAPIConstraint(false)] - public async Task GetPaymentRequests(ListPaymentRequestsViewModel model = null) + [HttpGet("/stores/{storeId}/payment-requests")] + public async Task GetPaymentRequests(string storeId, ListPaymentRequestsViewModel model = null) { model = this.ParseListQuery(model ?? new ListPaymentRequestsViewModel()); var includeArchived = new SearchString(model.SearchTerm).GetFilterBool("includearchived") == true; - var result = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery() + var result = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery { UserId = GetUserId(), + StoreId = CurrentStore.Id, Skip = model.Skip, Count = model.Count, IncludeArchived = includeArchived @@ -81,40 +80,30 @@ namespace BTCPayServer.Controllers return View(model); } - [HttpGet("edit/{id?}")] - public async Task EditPaymentRequest(string id) + [HttpGet("/stores/{storeId}/payment-requests/edit/{payReqId?}")] + public async Task EditPaymentRequest(string storeId, string payReqId) { - var data = await _PaymentRequestRepository.FindPaymentRequest(id, GetUserId()); - if (data == null && !string.IsNullOrEmpty(id)) + var data = await _PaymentRequestRepository.FindPaymentRequest(payReqId, GetUserId()); + if (data == null && !string.IsNullOrEmpty(payReqId)) { return NotFound(); } - SelectList stores = new SelectList(await _StoreRepository.GetStoresByUserId(GetUserId()), nameof(StoreData.Id), - nameof(StoreData.StoreName), data?.StoreDataId); - if (!stores.Any()) + return View(nameof(EditPaymentRequest), new UpdatePaymentRequestViewModel(data) { - TempData.SetStatusMessageModel(new StatusMessageModel - { - Html = - $"Error: You need to create at least one store. Create store", - Severity = StatusMessageModel.StatusSeverity.Error - }); - return RedirectToAction("GetPaymentRequests"); - } - - return View(nameof(EditPaymentRequest), new UpdatePaymentRequestViewModel(data) { Stores = stores }); + StoreId = CurrentStore.Id + }); } - [HttpPost("edit/{id?}")] - public async Task EditPaymentRequest(string id, UpdatePaymentRequestViewModel viewModel) + [HttpPost("/stores/{storeId}/payment-requests/edit/{payReqId?}")] + public async Task EditPaymentRequest(string payReqId, UpdatePaymentRequestViewModel viewModel) { if (string.IsNullOrEmpty(viewModel.Currency) || _Currencies.GetCurrencyData(viewModel.Currency, false) == null) ModelState.AddModelError(nameof(viewModel.Currency), "Invalid currency"); - var data = await _PaymentRequestRepository.FindPaymentRequest(id, GetUserId()); - if (data == null && !string.IsNullOrEmpty(id)) + var data = await _PaymentRequestRepository.FindPaymentRequest(payReqId, GetUserId()); + if (data == null && !string.IsNullOrEmpty(payReqId)) { return NotFound(); } @@ -126,10 +115,6 @@ namespace BTCPayServer.Controllers if (!ModelState.IsValid) { - viewModel.Stores = new SelectList(await _StoreRepository.GetStoresByUserId(GetUserId()), - nameof(StoreData.Id), - nameof(StoreData.StoreName), data?.StoreDataId); - return View(nameof(EditPaymentRequest), viewModel); } @@ -153,7 +138,7 @@ namespace BTCPayServer.Controllers blob.AllowCustomPaymentAmounts = viewModel.AllowCustomPaymentAmounts; data.SetBlob(blob); - if (string.IsNullOrEmpty(id)) + if (string.IsNullOrEmpty(payReqId)) { data.Created = DateTimeOffset.UtcNow; } @@ -162,14 +147,14 @@ namespace BTCPayServer.Controllers _EventAggregator.Publish(new PaymentRequestUpdated { Data = data, PaymentRequestId = data.Id, }); TempData[WellKnownTempData.SuccessMessage] = "Saved"; - return RedirectToAction(nameof(EditPaymentRequest), new { id = data.Id }); + return RedirectToAction(nameof(EditPaymentRequest), new { storeId = CurrentStore.Id, payReqId = data.Id }); } - [HttpGet("{id}")] + [HttpGet("{payReqId}")] [AllowAnonymous] - public async Task ViewPaymentRequest(string id) + public async Task ViewPaymentRequest(string payReqId) { - var result = await _PaymentRequestService.GetPaymentRequest(id, GetUserId()); + var result = await _PaymentRequestService.GetPaymentRequest(payReqId, GetUserId()); if (result == null) { return NotFound(); @@ -179,9 +164,9 @@ namespace BTCPayServer.Controllers return View(result); } - [HttpGet("{id}/pay")] + [HttpGet("{payReqId}/pay")] [AllowAnonymous] - public async Task PayPaymentRequest(string id, bool redirectToInvoice = true, + public async Task PayPaymentRequest(string payReqId, bool redirectToInvoice = true, decimal? amount = null, CancellationToken cancellationToken = default) { if (amount.HasValue && amount.Value <= 0) @@ -189,7 +174,7 @@ namespace BTCPayServer.Controllers return BadRequest("Please provide an amount greater than 0"); } - var result = await _PaymentRequestService.GetPaymentRequest(id, GetUserId()); + var result = await _PaymentRequestService.GetPaymentRequest(payReqId, GetUserId()); if (result == null) { return NotFound(); @@ -199,7 +184,7 @@ namespace BTCPayServer.Controllers { if (redirectToInvoice) { - return RedirectToAction("ViewPaymentRequest", new { Id = id }); + return RedirectToAction("ViewPaymentRequest", new { Id = payReqId }); } return BadRequest("Payment Request cannot be paid as it has been archived"); @@ -210,7 +195,7 @@ namespace BTCPayServer.Controllers { if (redirectToInvoice) { - return RedirectToAction("ViewPaymentRequest", new { Id = id }); + return RedirectToAction("ViewPaymentRequest", new { Id = payReqId }); } return BadRequest("Payment Request has already been settled."); @@ -220,7 +205,7 @@ namespace BTCPayServer.Controllers { if (redirectToInvoice) { - return RedirectToAction("ViewPaymentRequest", new { Id = id }); + return RedirectToAction("ViewPaymentRequest", new { Id = payReqId }); } return BadRequest("Payment Request has expired"); @@ -249,18 +234,18 @@ namespace BTCPayServer.Controllers else amount = result.AmountDue; - var pr = await _PaymentRequestRepository.FindPaymentRequest(id, null, cancellationToken); + var pr = await _PaymentRequestRepository.FindPaymentRequest(payReqId, null, cancellationToken); var blob = pr.GetBlob(); var store = pr.StoreData; try { - var redirectUrl = _linkGenerator.PaymentRequestLink(id, Request.Scheme, Request.Host, Request.PathBase); + var redirectUrl = _linkGenerator.PaymentRequestLink(payReqId, Request.Scheme, Request.Host, Request.PathBase); var invoiceMetadata = new InvoiceMetadata { - OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(id), - PaymentRequestId = id, + OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(payReqId), + PaymentRequestId = payReqId, BuyerEmail = result.Email }; @@ -273,7 +258,7 @@ namespace BTCPayServer.Controllers Checkout = {RedirectURL = redirectUrl} }; - var additionalTags = new List {PaymentRequestRepository.GetInternalTag(id)}; + var additionalTags = new List {PaymentRequestRepository.GetInternalTag(payReqId)}; var newInvoice = await _InvoiceController.CreateInvoiceCoreRaw(invoiceRequest,store, Request.GetAbsoluteRoot(), additionalTags, cancellationToken); if (redirectToInvoice) @@ -289,10 +274,10 @@ namespace BTCPayServer.Controllers } } - [HttpGet("{id}/cancel")] - public async Task CancelUnpaidPendingInvoice(string id, bool redirect = true) + [HttpGet("{payReqId}/cancel")] + public async Task CancelUnpaidPendingInvoice(string payReqId, bool redirect = true) { - var result = await _PaymentRequestService.GetPaymentRequest(id, GetUserId()); + var result = await _PaymentRequestService.GetPaymentRequest(payReqId, GetUserId()); if (result == null) { return NotFound(); @@ -318,21 +303,16 @@ namespace BTCPayServer.Controllers if (redirect) { TempData[WellKnownTempData.SuccessMessage] = "Payment cancelled"; - return RedirectToAction(nameof(ViewPaymentRequest), new { Id = id }); + return RedirectToAction(nameof(ViewPaymentRequest), new { Id = payReqId }); } return Ok("Payment cancelled"); } - private string GetUserId() + [HttpGet("{payReqId}/clone")] + public async Task ClonePaymentRequest(string payReqId) { - return _UserManager.GetUserId(User); - } - - [HttpGet("{id}/clone")] - public async Task ClonePaymentRequest(string id) - { - var result = await EditPaymentRequest(id); + var result = await EditPaymentRequest(CurrentStore.Id, payReqId); if (result is ViewResult viewResult) { var model = (UpdatePaymentRequestViewModel)viewResult.Model; @@ -346,15 +326,15 @@ namespace BTCPayServer.Controllers return NotFound(); } - [HttpGet("{id}/archive")] - public async Task TogglePaymentRequestArchival(string id) + [HttpGet("{payReqId}/archive")] + public async Task TogglePaymentRequestArchival(string payReqId) { - var result = await EditPaymentRequest(id); + var result = await EditPaymentRequest(CurrentStore.Id, payReqId); if (result is ViewResult viewResult) { var model = (UpdatePaymentRequestViewModel)viewResult.Model; model.Archived = !model.Archived; - await EditPaymentRequest(id, model); + await EditPaymentRequest(payReqId, model); TempData[WellKnownTempData.SuccessMessage] = model.Archived ? "The payment request has been archived and will no longer appear in the payment request list by default again." : "The payment request has been unarchived and will appear in the payment request list by default."; @@ -363,5 +343,15 @@ namespace BTCPayServer.Controllers return NotFound(); } + + private string GetUserId() + { + return _UserManager.GetUserId(User); + } + + private StoreData CurrentStore + { + get => HttpContext.GetStoreData(); + } } } diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 5659ac11e..9c66106ec 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -2,12 +2,9 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Net.Http; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; -using BTCPayServer.Abstractions.Extensions; -using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; using BTCPayServer.Configuration; using BTCPayServer.Data; @@ -17,7 +14,6 @@ using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Payments; using BTCPayServer.Payments.Lightning; using BTCPayServer.Rating; -using BTCPayServer.Security; using BTCPayServer.Security.Bitpay; using BTCPayServer.Services; using BTCPayServer.Services.Apps; @@ -25,18 +21,13 @@ using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using BTCPayServer.Services.Wallets; -using BundlerMinifier.TagHelpers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.Extensions.Options; using NBitcoin; using NBitcoin.DataEncoders; -using NBXplorer; -using NBXplorer.DerivationStrategy; using StoreData = BTCPayServer.Data.StoreData; namespace BTCPayServer.Controllers @@ -513,7 +504,7 @@ namespace BTCPayServer.Controllers }); } - private void AddPaymentMethods(StoreData store, StoreBlob storeBlob, + internal void AddPaymentMethods(StoreData store, StoreBlob storeBlob, out List derivationSchemes, out List lightningNodes) { var excludeFilters = storeBlob.GetExcludedPaymentMethods(); @@ -850,13 +841,6 @@ namespace BTCPayServer.Controllers var userId = GetUserId(); if (string.IsNullOrWhiteSpace(userId)) return Challenge(AuthenticationSchemes.Cookie); - var storeId = CurrentStore?.Id; - if (storeId != null) - { - var store = await _Repo.FindStore(storeId, userId); - if (store != null) - HttpContext.SetStoreData(store); - } var model = new CreateTokenViewModel(); ViewBag.HidePublicKey = true; ViewBag.ShowStores = true; diff --git a/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs b/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs index 36e7d1160..89228988b 100644 --- a/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs @@ -26,18 +26,7 @@ namespace BTCPayServer.Models.AppViewModels public string AppName { get; set; } [Display(Name = "Store")] - public string SelectedStore { get; set; } - - public void SetStores(StoreData[] stores) - { - var defaultStore = stores[0].Id; - var choices = stores.Select(o => new Format() { Name = o.StoreName, Value = o.Id }).ToArray(); - var chosen = choices.FirstOrDefault(f => f.Value == defaultStore) ?? choices.FirstOrDefault(); - Stores = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen); - SelectedStore = chosen.Value; - } - - public SelectList Stores { get; set; } + public string StoreId { get; set; } [Display(Name = "App Type")] public string SelectedAppType { get; set; } diff --git a/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs b/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs index 35cbbbe70..97e7854eb 100644 --- a/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs +++ b/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs @@ -9,6 +9,7 @@ namespace BTCPayServer.Models.InvoicingModels { public List Invoices { get; set; } = new List(); public string[] StoreIds { get; set; } + public string StoreId { get; set; } } public class InvoiceModel diff --git a/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs b/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs index f6dc662ea..fc6f7a536 100644 --- a/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs +++ b/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs @@ -13,7 +13,6 @@ namespace BTCPayServer.Models.PaymentRequestViewModels public class ListPaymentRequestsViewModel : BasePagingViewModel { public List Items { get; set; } - } public class UpdatePaymentRequestViewModel @@ -82,6 +81,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels public ViewPaymentRequestViewModel(PaymentRequestData data) { Id = data.Id; + StoreId = data.StoreDataId; var blob = data.GetBlob(); Archived = data.Archived; Title = blob.Title; @@ -121,6 +121,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels public string AmountDueFormatted { get; set; } public decimal Amount { get; set; } public string Id { get; set; } + public string StoreId { get; set; } public string Currency { get; set; } public DateTime? ExpiryDate { get; set; } public string Title { get; set; } diff --git a/BTCPayServer/Security/CookieAuthorizationHandler.cs b/BTCPayServer/Security/CookieAuthorizationHandler.cs index a15d38ca9..51b106f46 100644 --- a/BTCPayServer/Security/CookieAuthorizationHandler.cs +++ b/BTCPayServer/Security/CookieAuthorizationHandler.cs @@ -2,10 +2,14 @@ using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Client; using BTCPayServer.Data; +using BTCPayServer.PaymentRequest; +using BTCPayServer.Services.Apps; +using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Routing; namespace BTCPayServer.Security { @@ -14,14 +18,23 @@ namespace BTCPayServer.Security private readonly HttpContext _HttpContext; private readonly UserManager _userManager; private readonly StoreRepository _storeRepository; + private readonly AppService _appService; + private readonly PaymentRequestService _paymentRequestService; + private readonly InvoiceRepository _invoiceRepository; public CookieAuthorizationHandler(IHttpContextAccessor httpContextAccessor, UserManager userManager, - StoreRepository storeRepository) + StoreRepository storeRepository, + AppService appService, + InvoiceRepository invoiceRepository, + PaymentRequestService paymentRequestService) { _HttpContext = httpContextAccessor.HttpContext; _userManager = userManager; + _appService = appService; _storeRepository = storeRepository; + _invoiceRepository = invoiceRepository; + _paymentRequestService = paymentRequestService; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement) { @@ -37,15 +50,46 @@ namespace BTCPayServer.Security return; } - string storeId = context.Resource is string s? s :_HttpContext.GetImplicitStoreId(); + string storeId = context.Resource is string s ? s : _HttpContext.GetImplicitStoreId(); if (storeId == null) - return; - + { + var routeData = _HttpContext.GetRouteData(); + if (routeData != null) + { + // resolve from app + if (routeData.Values.TryGetValue("appId", out var vAppId)) + { + string appId = vAppId as string; + var app = await _appService.GetApp(appId, null); + storeId = app?.StoreDataId; + } + // resolve from payment request + else if (routeData.Values.TryGetValue("payReqId", out var vPayReqId)) + { + string payReqId = vPayReqId as string; + var paymentRequest = await _paymentRequestService.GetPaymentRequest(payReqId); + storeId = paymentRequest?.StoreId; + } + // resolve from app + if (routeData.Values.TryGetValue("invoiceId", out var vInvoiceId)) + { + string invoiceId = vInvoiceId as string; + var invoice = await _invoiceRepository.GetInvoice(invoiceId); + storeId = invoice?.StoreId; + } + } + + // store could not be found + if (storeId == null) + { + return; + } + } + var userid = _userManager.GetUserId(context.User); if (string.IsNullOrEmpty(userid)) return; - var store = await _storeRepository.FindStore(storeId, userid); bool success = false; diff --git a/BTCPayServer/Views/Account/AccessDenied.cshtml b/BTCPayServer/Views/Account/AccessDenied.cshtml index 3c5646ef4..9a0f70467 100644 --- a/BTCPayServer/Views/Account/AccessDenied.cshtml +++ b/BTCPayServer/Views/Account/AccessDenied.cshtml @@ -2,14 +2,11 @@ ViewData["Title"] = "Access denied"; } -
-
-
-
-

@ViewData["Title"]

-
-

You do not have access to this resource.

-
+
+
+
+

@ViewData["Title"]

+

You do not have access to this resource.

-
+ diff --git a/BTCPayServer/Views/Account/ForgotPassword.cshtml b/BTCPayServer/Views/Account/ForgotPassword.cshtml index fdd5f9951..84c3b8132 100644 --- a/BTCPayServer/Views/Account/ForgotPassword.cshtml +++ b/BTCPayServer/Views/Account/ForgotPassword.cshtml @@ -4,36 +4,32 @@ Layout = "_LayoutSimple"; } -
-
- + -
-
-

@ViewData["Title"]

-
-
-
-
-
- -

Start password reset

-

- We all forget passwords every now and then. Just provide email address tied to - your account and we'll start the process of helping you recover your account. -

-
-
- - - -
- - -
-
+
+
+

@ViewData["Title"]

+
-
+ +
+
+
+

Start password reset

+

+ We all forget passwords every now and then. Just provide email address tied to + your account and we'll start the process of helping you recover your account. +

+
+
+ + + +
+ +
+
+
@section PageFootContent { diff --git a/BTCPayServer/Views/Account/ForgotPasswordConfirmation.cshtml b/BTCPayServer/Views/Account/ForgotPasswordConfirmation.cshtml index a03e00f86..7f26551b9 100644 --- a/BTCPayServer/Views/Account/ForgotPasswordConfirmation.cshtml +++ b/BTCPayServer/Views/Account/ForgotPasswordConfirmation.cshtml @@ -2,21 +2,11 @@ ViewData["Title"] = "Email sent!"; } -

-

-

- - -
-
-
-
-

@ViewData["Title"]

-
-

- Please check your email to reset your password. -

-
-
+
+
+

@ViewData["Title"]

+

+ Please check your email to reset your password. +

-
+ diff --git a/BTCPayServer/Views/Account/Lockout.cshtml b/BTCPayServer/Views/Account/Lockout.cshtml index d7bc0e07e..40a8e87b4 100644 --- a/BTCPayServer/Views/Account/Lockout.cshtml +++ b/BTCPayServer/Views/Account/Lockout.cshtml @@ -1,19 +1,13 @@ @{ ViewData["Title"] = "Locked out"; } -
-
-
-
-

@ViewData["Title"]

-
-
- -
-

This account has been locked out because of multiple invalid login attempts. Please try again later.

-
- -
+
+
+

@ViewData["Title"]

+
-
+
+

This account has been locked out because of multiple invalid login attempts. Please try again later.

+
+ diff --git a/BTCPayServer/Views/Account/LoginWith2fa.cshtml b/BTCPayServer/Views/Account/LoginWith2fa.cshtml index 9c12acf7e..3c67fa7af 100644 --- a/BTCPayServer/Views/Account/LoginWith2fa.cshtml +++ b/BTCPayServer/Views/Account/LoginWith2fa.cshtml @@ -1,44 +1,39 @@ @model LoginWith2faViewModel -
-
-
-
-

Two-factor authentication

-
-
-
-
-
-
- -
- - - -
-
-
- -
-
-
- -
-
-
-
-
-

- Don't have access to your authenticator device? -
- You can log in with a recovery code. -

-
-
+
+
+

Two-factor authentication

+
-
- + +
+
+
+ +
+ + + +
+
+
+ +
+
+
+ +
+
+
+
+
+

+ Don't have access to your authenticator device? +
+ You can log in with a recovery code. +

+
+
diff --git a/BTCPayServer/Views/Account/LoginWithFido2.cshtml b/BTCPayServer/Views/Account/LoginWithFido2.cshtml index 836a29c16..75c756886 100644 --- a/BTCPayServer/Views/Account/LoginWithFido2.cshtml +++ b/BTCPayServer/Views/Account/LoginWithFido2.cshtml @@ -7,22 +7,18 @@ -
-
-
-
-

FIDO2 Authentication

-
-
- -

Insert your security key into your computer's USB port. If it has a button, tap on it.

-
-

- -
+
+
+

FIDO2 Authentication

+
+
+ +

Insert your security key into your computer's USB port. If it has a button, tap on it.

+

+
-
+ } -
-
- + -

@ViewData["Title"]

+

@ViewData["Title"]

-
-
-
-
-
- - - +
+
+ +
+ @if (Model.StoreId != null) + { + + } + else + { +
+ + + +
+ +

Invoice Details

+ } +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + @foreach (var item in Model.AvailablePaymentMethods) + { +
+
-

Invoice Details

-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - @foreach (var item in Model.AvailablePaymentMethods) - { -
- + } + +
+
+ + + +
+

Customer Information

+
+ + + +
+
+ + + +
+ +

Additional Options

+
+
+
+

+ +

+
+

Custom data to correlate the invoice with an order. This data can be a simple text, number or JSON object, e.g. { "orderId": 615, "product": "Pizza" }

+
+ + +
- } - +
-
- - - -
-

Customer Information

-
- - - -
-
- - - -
- -

Additional Options

-
-
-
-

- -

-
-

Custom data to correlate the invoice with an order. This data can be a simple text, number or JSON object, e.g. { "orderId": 615, "product": "Pizza" }

-
- - - -
+
+

+ +

+
+
+
+ + +
-
-
-

- -

-
-
-
- - - -
-
- - - -

- Receive updates for this invoice. -

-
-
+
+ + + +

+ Receive updates for this invoice. +

- -
- - Back to list -
- +
-
+ +
+ + Back to list +
+
-
+ diff --git a/BTCPayServer/Views/Invoice/Invoice.cshtml b/BTCPayServer/Views/Invoice/Invoice.cshtml index fed06c486..f1d3fd73f 100644 --- a/BTCPayServer/Views/Invoice/Invoice.cshtml +++ b/BTCPayServer/Views/Invoice/Invoice.cshtml @@ -44,353 +44,351 @@ } -
-
+
-
-

@ViewData["Title"]

-
-
- @if (Model.ShowCheckout) - { - - - Checkout - - } - @if (Model.CanRefund) - { - Issue Refund - } - else - { - - } -
- -
-
-
-
- -
-
-

Invoice Information

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @if (!string.IsNullOrEmpty(Model.RefundEmail)) - { - - - - - } - @if (!string.IsNullOrEmpty(Model.NotificationUrl)) - { - - - - - } - @if (!string.IsNullOrEmpty(Model.RedirectUrl)) - { - - - - - } -
Store@Model.StoreName
Invoice Id@Model.Id
Order Id - @if (string.IsNullOrEmpty(Model.TypedMetadata.OrderUrl)) - { - @Model.TypedMetadata.OrderId - } - else - { - - @if (string.IsNullOrEmpty(Model.TypedMetadata.OrderId)) - { - View Order - } - else - { - @Model.TypedMetadata.OrderId - } - - } -
Payment Request Id@Model.TypedMetadata.PaymentRequestId
State - @if (Model.CanMarkStatus) - { - - } - else - { - @Model.State - @if (Model.StatusException != InvoiceExceptionStatus.None) - { - @String.Format(" ({0})", Model.StatusException.ToString()); - } - } -
Created Date@Model.CreatedDate.ToBrowserDate()
Expiration Date@Model.ExpirationDate.ToBrowserDate()
Monitoring Date@Model.MonitoringDate.ToBrowserDate()
Transaction Speed@Model.TransactionSpeed
Total Fiat Due@Model.Fiat
Refund Email@Model.RefundEmail
Notification Url@Model.NotificationUrl
Redirect Url@Model.RedirectUrl
- @if (Model.PosData.Count == 0) +
+

@ViewData["Title"]

+
+
+ @if (Model.ShowCheckout) { -

Product Information

- - @if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemCode)) - { - - - - - } - @if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemDesc)) - { - - - - - } - - - - - - - - -
Item code@Model.TypedMetadata.ItemCode
Item Description@Model.TypedMetadata.ItemDesc
Price@Model.Fiat
Tax Included@Model.TaxIncluded
+ + + Checkout + } -
-
-

Buyer Information

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Name@Model.TypedMetadata.BuyerName
Email@Model.TypedMetadata.BuyerEmail
Phone@Model.TypedMetadata.BuyerPhone
Address 1@Model.TypedMetadata.BuyerAddress1
Address 2@Model.TypedMetadata.BuyerAddress2
City@Model.TypedMetadata.BuyerCity
State@Model.TypedMetadata.BuyerState
Country@Model.TypedMetadata.BuyerCountry
Zip@Model.TypedMetadata.BuyerZip
-
-
- - @if (Model.PosData.Count != 0) - { -
-
-

Product information

- - @if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemCode)) - { - - - - - } - @if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemDesc)) - { - - - - - } - - - - - - - - -
Item code@Model.TypedMetadata.ItemCode
Item Description@Model.TypedMetadata.ItemDesc
Price@Model.Fiat
Tax included@Model.TaxIncluded
-
-
-

Point of Sale Data

- - -
-
- } - - - - @if (Model.Deliveries.Count != 0) - { -

Webhook deliveries

-
    - @foreach (var delivery in Model.Deliveries) + @if (Model.CanRefund) { -
  • -
    -
    - - @if (delivery.Success) - { - - } - else - { - - } - - - @delivery.Id - - | - @delivery.Type - | - - - - - @delivery.PayloadUrl - - | - - @delivery.Time.ToBrowserDate() - - - - -
    -
    -
  • + Issue Refund } -
- } -
-
-

Events

- - - - - - - - - @foreach (var evt in Model.Events) + else + { + + } + + - - - + Unarchive } - -
DateMessage
@evt.Timestamp.ToBrowserDate()@evt.Message
+ else + { + Archive + } + +
-
+ +
+
+

Invoice Information

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @if (!string.IsNullOrEmpty(Model.RefundEmail)) + { + + + + + } + @if (!string.IsNullOrEmpty(Model.NotificationUrl)) + { + + + + + } + @if (!string.IsNullOrEmpty(Model.RedirectUrl)) + { + + + + + } +
Store@Model.StoreName
Invoice Id@Model.Id
Order Id + @if (string.IsNullOrEmpty(Model.TypedMetadata.OrderUrl)) + { + @Model.TypedMetadata.OrderId + } + else + { + + @if (string.IsNullOrEmpty(Model.TypedMetadata.OrderId)) + { + View Order + } + else + { + @Model.TypedMetadata.OrderId + } + + } +
Payment Request Id@Model.TypedMetadata.PaymentRequestId
State + @if (Model.CanMarkStatus) + { + + } + else + { + @Model.State + @if (Model.StatusException != InvoiceExceptionStatus.None) + { + @String.Format(" ({0})", Model.StatusException.ToString()); + } + } +
Created Date@Model.CreatedDate.ToBrowserDate()
Expiration Date@Model.ExpirationDate.ToBrowserDate()
Monitoring Date@Model.MonitoringDate.ToBrowserDate()
Transaction Speed@Model.TransactionSpeed
Total Fiat Due@Model.Fiat
Refund Email@Model.RefundEmail
Notification Url@Model.NotificationUrl
Redirect Url@Model.RedirectUrl
+ @if (Model.PosData.Count == 0) + { +

Product Information

+ + @if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemCode)) + { + + + + + } + @if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemDesc)) + { + + + + + } + + + + + + + + +
Item code@Model.TypedMetadata.ItemCode
Item Description@Model.TypedMetadata.ItemDesc
Price@Model.Fiat
Tax Included@Model.TaxIncluded
+ } +
+
+

Buyer Information

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name@Model.TypedMetadata.BuyerName
Email@Model.TypedMetadata.BuyerEmail
Phone@Model.TypedMetadata.BuyerPhone
Address 1@Model.TypedMetadata.BuyerAddress1
Address 2@Model.TypedMetadata.BuyerAddress2
City@Model.TypedMetadata.BuyerCity
State@Model.TypedMetadata.BuyerState
Country@Model.TypedMetadata.BuyerCountry
Zip@Model.TypedMetadata.BuyerZip
+
+
+ + @if (Model.PosData.Count != 0) + { +
+
+

Product information

+ + @if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemCode)) + { + + + + + } + @if (!string.IsNullOrEmpty(Model.TypedMetadata.ItemDesc)) + { + + + + + } + + + + + + + + +
Item code@Model.TypedMetadata.ItemCode
Item Description@Model.TypedMetadata.ItemDesc
Price@Model.Fiat
Tax included@Model.TaxIncluded
+
+
+

Point of Sale Data

+ + +
+
+ } + + + + @if (Model.Deliveries.Count != 0) + { +

Webhook deliveries

+
    + @foreach (var delivery in Model.Deliveries) + { +
  • +
    +
    + + @if (delivery.Success) + { + + } + else + { + + } + + + @delivery.Id + + | + @delivery.Type + | + + + + + @delivery.PayloadUrl + + | + + @delivery.Time.ToBrowserDate() + + + + +
    +
    +
  • + } +
+ } +
+
+

Events

+ + + + + + + + + @foreach (var evt in Model.Events) + { + + + + + } + +
DateMessage
@evt.Timestamp.ToBrowserDate()@evt.Message
+
+
+ diff --git a/BTCPayServer/Views/Invoice/ListInvoices.cshtml b/BTCPayServer/Views/Invoice/ListInvoices.cshtml index 2b8a8cae7..d2f4ffbb4 100644 --- a/BTCPayServer/Views/Invoice/ListInvoices.cshtml +++ b/BTCPayServer/Views/Invoice/ListInvoices.cshtml @@ -178,286 +178,287 @@ } @Html.HiddenFor(a => a.Count) -
-
- - - - -
-
- - - -
- - - - - - + + + - + + + + @* Custom Range Modal *@ +
+ + +
+
+

+ You can search for invoice Id, deposit address, price, order id, store id, any buyer information and any product information. + Be sure to split your search parameters with comma, for example:
+ startdate:2019-04-25 13:00:00, status:paid +

+

+ You can also apply filters to your search by searching for filtername:value, supported filters are: +

+
    +
  • storeid:id for filtering a specific store
  • +
  • orderid:id for filtering a specific order
  • +
  • itemcode:code for filtering a specific type of item purchased through the pos or crowdfund apps
  • +
  • status:(expired|invalid|complete|confirmed|paid|new) for filtering a specific status
  • +
  • exceptionstatus:(paidover|paidlate|paidpartial) for filtering a specific exception state
  • +
  • unusual:(true|false) for filtering invoices which might requires merchant attention (those invalid or with an exceptionstatus)
  • +
  • startdate:yyyy-MM-dd HH:mm:ss getting invoices that were created after certain date
  • +
  • enddate:yyyy-MM-dd HH:mm:ss getting invoices that were created before certain date
  • +
+
+
+ +@if (Model.Total > 0) +{ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + @foreach (var invoice in Model.Invoices) + { + + + + + + + + + + + + + } + +
+ + + Date + + + + OrderIdInvoiceIdStatusAmountActions
+ + + + @invoice.Date.ToBrowserDate() + + + @if (invoice.RedirectUrl != string.Empty) + { + @invoice.OrderId + } + else + { + @invoice.OrderId + } + @invoice.InvoiceId + @if (invoice.Details.Archived) + { + archived + } + @if (invoice.CanMarkStatus) + { +
+ + +
+ } + else + { + + @invoice.Status.Status.ToModernStatus().ToString() @* @invoice.Status.ToString().ToLower() *@ + @if (invoice.Status.ExceptionStatus != InvoiceExceptionStatus.None) + { + @String.Format("({0})", @invoice.Status.ExceptionStatus.ToString()) + ; + } + + } + @foreach (var paymentType in invoice.Details.Payments.Select(payment => payment.GetPaymentMethodId()?.PaymentType).Distinct().Where(type => type != null && !string.IsNullOrEmpty(type.GetBadge()))) + { + @paymentType.GetBadge() + } +
@invoice.AmountCurrency + @if (invoice.ShowCheckout) + { + + Checkout + [^] + @if (!invoice.CanMarkStatus) + { + - + } + + } +   + Details + + + +
+ + + +} +else +{ +

+ There are no invoices matching your criteria. +

+} diff --git a/BTCPayServer/Views/Invoice/Refund.cshtml b/BTCPayServer/Views/Invoice/Refund.cshtml index da3933c4b..d736fb471 100644 --- a/BTCPayServer/Views/Invoice/Refund.cshtml +++ b/BTCPayServer/Views/Invoice/Refund.cshtml @@ -3,92 +3,90 @@ ViewData["Title"] = "Refund"; } -
-
-
+ diff --git a/BTCPayServer/Views/LightningLikePayout/ConfirmLightningPayout.cshtml b/BTCPayServer/Views/LightningLikePayout/ConfirmLightningPayout.cshtml index 5d4b2a11f..f4bd187bb 100644 --- a/BTCPayServer/Views/LightningLikePayout/ConfirmLightningPayout.cshtml +++ b/BTCPayServer/Views/LightningLikePayout/ConfirmLightningPayout.cshtml @@ -4,31 +4,29 @@ ViewData["Title"] = "Confirm Lightning Payout"; var cryptoCode = Context.GetRouteValue("cryptoCode"); } -
-
-

@ViewData["Title"]

-
-
-
    - @foreach (var item in Model) - { -
  • -
    @item.Destination
    +

    @ViewData["Title"]

    +
    +
    +
      + @foreach (var item in Model) + { +
    • +
      @item.Destination
      - @item.Amount @cryptoCode -
    • + @item.Amount @cryptoCode + -
      - - -
      - } +
      + + +
      + } -
    -
    -
    +
+
+ @section PageFootContent { @if (!notificationDisabled) { - + } + diff --git a/BTCPayServer/Views/Shared/_NavLayout.cshtml b/BTCPayServer/Views/Shared/_NavLayout.cshtml index d5af3d02b..09f2a1e4c 100644 --- a/BTCPayServer/Views/Shared/_NavLayout.cshtml +++ b/BTCPayServer/Views/Shared/_NavLayout.cshtml @@ -2,7 +2,6 @@ Layout = "/Views/Shared/_Layout.cshtml"; ViewBag.ShowMenu = ViewBag.ShowMenu ?? true; ViewBag.ShowMainTitle = ViewBag.ShowMainTitle ?? true; - ViewBag.ShowBreadcrumb = ViewBag.ShowBreadcrumb ?? false; if (!ViewData.ContainsKey("NavPartialName")) { ViewData["NavPartialName"] = "_Nav"; @@ -17,42 +16,17 @@ @await RenderSectionAsync("PageFootContent", false) } -
-
- @if (ViewBag.ShowBreadcrumb) - { - - } - else if (!string.IsNullOrEmpty(ViewData["MainTitle"] as string)) - { -

@ViewData["MainTitle"]

- } -
- @if (ViewBag.ShowMenu) - { -
- -
- } -
- - @RenderBody() -
-
-
-
+@if (!string.IsNullOrEmpty(ViewData["MainTitle"] as string)) +{ +

@ViewData["MainTitle"]

+} +@if (ViewBag.ShowMenu) +{ + +} + + + +@RenderBody() diff --git a/BTCPayServer/Views/Stores/GeneralSettings.cshtml b/BTCPayServer/Views/Stores/GeneralSettings.cshtml index 5abe40a09..0a4ca56d4 100644 --- a/BTCPayServer/Views/Stores/GeneralSettings.cshtml +++ b/BTCPayServer/Views/Stores/GeneralSettings.cshtml @@ -58,7 +58,7 @@ {

Other actions

}
diff --git a/BTCPayServer/Views/Stores/_Nav.cshtml b/BTCPayServer/Views/Stores/_Nav.cshtml index 5d890c59a..42028e82f 100644 --- a/BTCPayServer/Views/Stores/_Nav.cshtml +++ b/BTCPayServer/Views/Stores/_Nav.cshtml @@ -1,15 +1,13 @@ @using BTCPayServer.Client -