diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 1121bd6a0..459f358da 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -565,9 +565,11 @@ namespace BTCPayServer.Tests s.RegisterNewUser(true); s.CreateNewStore(); s.GoToInvoices(); - s.Driver.FindElement(By.Id("CreateNewInvoice")).Click(); + // Should give us an error message if we try to create an invoice before adding a wallet + s.Driver.FindElement(By.Id("CreateNewInvoice")).Click(); Assert.Contains("To create an invoice, you need to", s.Driver.PageSource); + s.AddDerivationScheme(); s.GoToInvoices(); s.CreateInvoice(); @@ -1195,8 +1197,13 @@ namespace BTCPayServer.Tests await s.StartAsync(); s.RegisterNewUser(); s.CreateNewStore(); - s.AddDerivationScheme(); + s.Driver.FindElement(By.Id("StoreNav-PaymentRequests")).Click(); + + // Should give us an error message if we try to create a payment request before adding a wallet + s.Driver.FindElement(By.Id("CreatePaymentRequest")).Click(); + Assert.Contains("To create a payment request, you need to", s.Driver.PageSource); + s.AddDerivationScheme(); s.Driver.FindElement(By.Id("StoreNav-PaymentRequests")).Click(); s.Driver.FindElement(By.Id("CreatePaymentRequest")).Click(); s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123"); diff --git a/BTCPayServer/Controllers/UIInvoiceController.UI.cs b/BTCPayServer/Controllers/UIInvoiceController.UI.cs index 05aa53d35..996006c78 100644 --- a/BTCPayServer/Controllers/UIInvoiceController.UI.cs +++ b/BTCPayServer/Controllers/UIInvoiceController.UI.cs @@ -1,11 +1,8 @@ #nullable enable using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; -using System.Net.Mime; using System.Net.WebSockets; -using System.Reflection.Metadata; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; @@ -32,10 +29,8 @@ using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; using NBitcoin; -using NBitpayClient; using NBXplorer; using Newtonsoft.Json.Linq; -using BitpayCreateInvoiceRequest = BTCPayServer.Models.BitpayCreateInvoiceRequest; using StoreData = BTCPayServer.Data.StoreData; namespace BTCPayServer.Controllers @@ -1149,63 +1144,34 @@ namespace BTCPayServer.Controllers }; } - private SelectList GetPaymentMethodsSelectList() - { - var store = GetCurrentStore(); - var excludeFilter = store.GetStoreBlob().GetExcludedPaymentMethods(); - - return new SelectList(store.GetSupportedPaymentMethods(_NetworkProvider) - .Where(s => !excludeFilter.Match(s.PaymentId)) - .Select(method => new SelectListItem(method.PaymentId.ToPrettyString(), method.PaymentId.ToString())), - nameof(SelectListItem.Value), - nameof(SelectListItem.Text)); - } - - private bool AnyPaymentMethodAvailable(StoreData store) - { - var storeBlob = store.GetStoreBlob(); - var excludeFilter = storeBlob.GetExcludedPaymentMethods(); - - return store.GetSupportedPaymentMethods(_NetworkProvider).Where(s => !excludeFilter.Match(s.PaymentId)).Any(); - } - [HttpGet("/stores/{storeId}/invoices/create")] [HttpGet("invoices/create")] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [BitpayAPIConstraint(false)] public async Task CreateInvoice(InvoicesModel? model = null) { - if (model?.StoreId != null) - { - var store = await _StoreRepository.FindStore(model.StoreId, GetUserId()); - if (store == null) - return NotFound(); - - if (!AnyPaymentMethodAvailable(store)) - { - TempData.SetStatusMessageModel(new StatusMessageModel - { - Severity = StatusMessageModel.StatusSeverity.Error, - Html = $"To create an invoice, you need to set up a wallet first", - AllowDismiss = false - }); - } - - HttpContext.SetStoreData(store); - } - else + if (string.IsNullOrEmpty(model?.StoreId)) { TempData[WellKnownTempData.ErrorMessage] = "You need to select a store before creating an invoice."; return RedirectToAction(nameof(UIHomeController.Index), "UIHome"); } - var storeBlob = HttpContext.GetStoreData()?.GetStoreBlob(); + var store = await _StoreRepository.FindStore(model.StoreId, GetUserId()); + if (store == null) + return NotFound(); + + if (!store.AnyPaymentMethodAvailable(_NetworkProvider)) + { + return NoPaymentMethodResult(store.Id); + } + + var storeBlob = store.GetStoreBlob(); var vm = new CreateInvoiceModel { StoreId = model.StoreId, - Currency = storeBlob?.DefaultCurrency, - CheckoutType = storeBlob?.CheckoutType ?? CheckoutType.V2, - AvailablePaymentMethods = GetPaymentMethodsSelectList() + Currency = storeBlob.DefaultCurrency, + CheckoutType = storeBlob.CheckoutType, + AvailablePaymentMethods = GetPaymentMethodsSelectList(store) }; return View(vm); @@ -1218,9 +1184,14 @@ namespace BTCPayServer.Controllers public async Task CreateInvoice(CreateInvoiceModel model, CancellationToken cancellationToken) { var store = HttpContext.GetStoreData(); + if (!store.AnyPaymentMethodAvailable(_NetworkProvider)) + { + return NoPaymentMethodResult(store.Id); + } + var storeBlob = store.GetStoreBlob(); model.CheckoutType = storeBlob.CheckoutType; - model.AvailablePaymentMethods = GetPaymentMethodsSelectList(); + model.AvailablePaymentMethods = GetPaymentMethodsSelectList(store); JObject? metadataObj = null; if (!string.IsNullOrEmpty(model.Metadata)) @@ -1239,18 +1210,6 @@ namespace BTCPayServer.Controllers { return View(model); } - - if (!AnyPaymentMethodAvailable(store)) - { - TempData.SetStatusMessageModel(new StatusMessageModel - { - Severity = StatusMessageModel.StatusSeverity.Error, - Html = $"To create an invoice, you need to set up a wallet first", - AllowDismiss = false - }); - return View(model); - } - try { var metadata = metadataObj is null ? new InvoiceMetadata() : InvoiceMetadata.FromJObject(metadataObj); @@ -1396,5 +1355,26 @@ namespace BTCPayServer.Controllers } } } + + private SelectList GetPaymentMethodsSelectList(StoreData store) + { + var excludeFilter = store.GetStoreBlob().GetExcludedPaymentMethods(); + return new SelectList(store.GetSupportedPaymentMethods(_NetworkProvider) + .Where(s => !excludeFilter.Match(s.PaymentId)) + .Select(method => new SelectListItem(method.PaymentId.ToPrettyString(), method.PaymentId.ToString())), + nameof(SelectListItem.Value), + nameof(SelectListItem.Text)); + } + + private IActionResult NoPaymentMethodResult(string storeId) + { + TempData.SetStatusMessageModel(new StatusMessageModel + { + Severity = StatusMessageModel.StatusSeverity.Error, + Html = $"To create an invoice, you need to set up a wallet first", + AllowDismiss = false + }); + return RedirectToAction(nameof(ListInvoices), new { storeId }); + } } } diff --git a/BTCPayServer/Controllers/UIPaymentRequestController.cs b/BTCPayServer/Controllers/UIPaymentRequestController.cs index b74fc19c4..62ae18c86 100644 --- a/BTCPayServer/Controllers/UIPaymentRequestController.cs +++ b/BTCPayServer/Controllers/UIPaymentRequestController.cs @@ -3,7 +3,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Form; +using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.Data; @@ -39,6 +41,7 @@ namespace BTCPayServer.Controllers private readonly DisplayFormatter _displayFormatter; private readonly InvoiceRepository _InvoiceRepository; private readonly StoreRepository _storeRepository; + private readonly BTCPayNetworkProvider _networkProvider; private FormComponentProviders FormProviders { get; } public FormDataService FormDataService { get; } @@ -54,7 +57,8 @@ namespace BTCPayServer.Controllers StoreRepository storeRepository, InvoiceRepository invoiceRepository, FormComponentProviders formProviders, - FormDataService formDataService) + FormDataService formDataService, + BTCPayNetworkProvider networkProvider) { _InvoiceController = invoiceController; _UserManager = userManager; @@ -67,6 +71,7 @@ namespace BTCPayServer.Controllers _InvoiceRepository = invoiceRepository; FormProviders = formProviders; FormDataService = formDataService; + _networkProvider = networkProvider; } [HttpGet("/stores/{storeId}/payment-requests")] @@ -107,12 +112,20 @@ namespace BTCPayServer.Controllers public async Task EditPaymentRequest(string storeId, string payReqId) { var store = GetCurrentStore(); + if (store == null) + { + return NotFound(); + } var paymentRequest = GetCurrentPaymentRequest(); if (paymentRequest == null && !string.IsNullOrEmpty(payReqId)) { return NotFound(); } - + if (!store.AnyPaymentMethodAvailable(_networkProvider)) + { + return NoPaymentMethodResult(storeId); + } + var storeBlob = store.GetStoreBlob(); var prInvoices = payReqId is null ? null : (await _PaymentRequestService.GetPaymentRequest(payReqId, GetUserId())).Invoices; var vm = new UpdatePaymentRequestViewModel(paymentRequest) @@ -143,7 +156,11 @@ namespace BTCPayServer.Controllers { return NotFound(); } - + if (!store.AnyPaymentMethodAvailable(_networkProvider)) + { + return NoPaymentMethodResult(store.Id); + } + if (paymentRequest?.Archived is true && viewModel.Archived) { ModelState.AddModelError(string.Empty, "You cannot edit an archived payment request."); @@ -441,5 +458,16 @@ namespace BTCPayServer.Controllers private StoreData GetCurrentStore() => HttpContext.GetStoreData(); private PaymentRequestData GetCurrentPaymentRequest() => HttpContext.GetPaymentRequestData(); + + private IActionResult NoPaymentMethodResult(string storeId) + { + TempData.SetStatusMessageModel(new StatusMessageModel + { + Severity = StatusMessageModel.StatusSeverity.Error, + Html = $"To create a payment request, you need to set up a wallet first", + AllowDismiss = false + }); + return RedirectToAction(nameof(GetPaymentRequests), new { storeId }); + } } } diff --git a/BTCPayServer/Data/StoreDataExtensions.cs b/BTCPayServer/Data/StoreDataExtensions.cs index 0eabf630b..c4ceb1d99 100644 --- a/BTCPayServer/Data/StoreDataExtensions.cs +++ b/BTCPayServer/Data/StoreDataExtensions.cs @@ -60,6 +60,14 @@ namespace BTCPayServer.Data return result; } + public static bool AnyPaymentMethodAvailable(this StoreData storeData, BTCPayNetworkProvider networkProvider) + { + var storeBlob = GetStoreBlob(storeData); + var excludeFilter = storeBlob.GetExcludedPaymentMethods(); + + return GetSupportedPaymentMethods(storeData, networkProvider).Where(s => !excludeFilter.Match(s.PaymentId)).Any(); + } + public static bool SetStoreBlob(this StoreData storeData, StoreBlob storeBlob) { var original = new Serializer(null).ToString(storeData.GetStoreBlob());