Apply better messages

This commit is contained in:
Kukks
2021-11-04 08:21:01 +01:00
parent 003927418c
commit 51f0c2a5f8
13 changed files with 116 additions and 44 deletions

View File

@@ -106,7 +106,7 @@ namespace BTCPayServer.Controllers.GreenField
PaymentMethodId.TryParse(s, out var pmi); PaymentMethodId.TryParse(s, out var pmi);
return pmi; return pmi;
}).ToArray(); }).ToArray();
var supported = _payoutHandlers.GetSupportedPaymentMethods().ToArray(); var supported = (await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData())).ToArray();
for (int i = 0; i < paymentMethods.Length; i++) for (int i = 0; i < paymentMethods.Length; i++)
{ {
if (!supported.Contains(paymentMethods[i])) if (!supported.Contains(paymentMethods[i]))

View File

@@ -167,13 +167,15 @@ namespace BTCPayServer.Controllers
[AllowAnonymous] [AllowAnonymous]
public async Task<IActionResult> Refund([FromServices]IEnumerable<IPayoutHandler> payoutHandlers, string invoiceId, CancellationToken cancellationToken) public async Task<IActionResult> Refund([FromServices]IEnumerable<IPayoutHandler> payoutHandlers, string invoiceId, CancellationToken cancellationToken)
{ {
using var ctx = _dbContextFactory.CreateContext(); await using var ctx = _dbContextFactory.CreateContext();
ctx.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTracking; ctx.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTracking;
var invoice = await ctx.Invoices.Include(i => i.Payments) var invoice = await ctx.Invoices.Include(i => i.Payments)
.Include(i => i.CurrentRefund) .Include(i => i.CurrentRefund)
.Include(i => i.StoreData)
.ThenInclude(data => data.UserStores)
.Include(i => i.CurrentRefund.PullPaymentData) .Include(i => i.CurrentRefund.PullPaymentData)
.Where(i => i.Id == invoiceId) .Where(i => i.Id == invoiceId)
.FirstOrDefaultAsync(cancellationToken: cancellationToken); .FirstOrDefaultAsync(cancellationToken);
if (invoice is null) if (invoice is null)
return NotFound(); return NotFound();
if (invoice.CurrentRefund?.PullPaymentDataId is null && GetUserId() is null) if (invoice.CurrentRefund?.PullPaymentDataId is null && GetUserId() is null)
@@ -191,7 +193,17 @@ namespace BTCPayServer.Controllers
{ {
var paymentMethods = invoice.GetBlob(_NetworkProvider).GetPaymentMethods(); var paymentMethods = invoice.GetBlob(_NetworkProvider).GetPaymentMethods();
var pmis = paymentMethods.Select(method => method.GetId()).ToList(); var pmis = paymentMethods.Select(method => method.GetId()).ToList();
var options = payoutHandlers.GetSupportedPaymentMethods(pmis); var options = (await payoutHandlers.GetSupportedPaymentMethods(invoice.StoreData)).Where(id => pmis.Contains(id)).ToList();
if (!options.Any())
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = "There were no payment methods available to provide refunds with for this invoice."
});
return RedirectToAction(nameof(Invoice), new { invoiceId });
}
var defaultRefund = invoice.Payments var defaultRefund = invoice.Payments
.Select(p => p.GetBlob(_NetworkProvider)) .Select(p => p.GetBlob(_NetworkProvider))
.Select(p => p?.GetPaymentMethodId()) .Select(p => p?.GetPaymentMethodId())

View File

@@ -60,12 +60,12 @@ namespace BTCPayServer.Controllers
} }
[HttpGet("new")] [HttpGet("new")]
public IActionResult NewPullPayment(string storeId) public async Task<IActionResult> NewPullPayment(string storeId)
{ {
if (CurrentStore is null) if (CurrentStore is null)
return NotFound(); return NotFound();
var storeMethods = CurrentStore.GetSupportedPaymentMethods(_btcPayNetworkProvider).Select(method => method.PaymentId).ToList();
var paymentMethodOptions = _payoutHandlers.GetSupportedPaymentMethods(storeMethods); var paymentMethodOptions = await _payoutHandlers.GetSupportedPaymentMethods(CurrentStore);
return View(new NewPullPaymentModel return View(new NewPullPaymentModel
{ {
Name = "", Name = "",
@@ -82,8 +82,7 @@ namespace BTCPayServer.Controllers
if (CurrentStore is null) if (CurrentStore is null)
return NotFound(); return NotFound();
var storeMethods = CurrentStore.GetSupportedPaymentMethods(_btcPayNetworkProvider).Select(method => method.PaymentId).ToList(); var paymentMethodOptions = await _payoutHandlers.GetSupportedPaymentMethods(CurrentStore);
var paymentMethodOptions = _payoutHandlers.GetSupportedPaymentMethods(storeMethods);
model.PaymentMethodItems = model.PaymentMethodItems =
paymentMethodOptions.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true)); paymentMethodOptions.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true));
model.Name ??= string.Empty; model.Name ??= string.Empty;
@@ -230,6 +229,8 @@ namespace BTCPayServer.Controllers
{ {
if (vm is null) if (vm is null)
return NotFound(); return NotFound();
vm.PaymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData());
var paymentMethodId = PaymentMethodId.Parse(vm.PaymentMethodId); var paymentMethodId = PaymentMethodId.Parse(vm.PaymentMethodId);
var handler = _payoutHandlers var handler = _payoutHandlers
.FindPayoutHandler(paymentMethodId); .FindPayoutHandler(paymentMethodId);
@@ -404,9 +405,11 @@ namespace BTCPayServer.Controllers
string storeId, string pullPaymentId, string paymentMethodId, PayoutState payoutState, string storeId, string pullPaymentId, string paymentMethodId, PayoutState payoutState,
int skip = 0, int count = 50) int skip = 0, int count = 50)
{ {
var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData());
var vm = this.ParseListQuery(new PayoutsModel var vm = this.ParseListQuery(new PayoutsModel
{ {
PaymentMethodId = paymentMethodId?? _payoutHandlers.GetSupportedPaymentMethods().First().ToString(), PaymentMethods = paymentMethods,
PaymentMethodId = paymentMethodId??paymentMethods.First().ToString(),
PullPaymentId = pullPaymentId, PullPaymentId = pullPaymentId,
PayoutState = payoutState, PayoutState = payoutState,
Skip = skip, Skip = skip,

View File

@@ -25,6 +25,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NewBlockEvent = BTCPayServer.Events.NewBlockEvent; using NewBlockEvent = BTCPayServer.Events.NewBlockEvent;
using PayoutData = BTCPayServer.Data.PayoutData; using PayoutData = BTCPayServer.Data.PayoutData;
using StoreData = BTCPayServer.Data.StoreData;
public class BitcoinLikePayoutHandler : IPayoutHandler public class BitcoinLikePayoutHandler : IPayoutHandler
{ {
@@ -215,11 +216,10 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
return null; return null;
} }
public IEnumerable<PaymentMethodId> GetSupportedPaymentMethods() public Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData)
{ {
return _btcPayNetworkProvider.GetAll().OfType<BTCPayNetwork>() return Task.FromResult(storeData.GetEnabledPaymentIds(_btcPayNetworkProvider)
.Where(network => network.ReadonlyWallet is false) .Where(id => id.PaymentType == BitcoinPaymentType.Instance));
.Select(network => new PaymentMethodId(network.CryptoCode, BitcoinPaymentType.Instance));
} }
public async Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId ,string[] payoutIds) public async Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId ,string[] payoutIds)

View File

@@ -7,6 +7,7 @@ using BTCPayServer.Data;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using PayoutData = BTCPayServer.Data.PayoutData; using PayoutData = BTCPayServer.Data.PayoutData;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using StoreData = BTCPayServer.Data.StoreData;
public interface IPayoutHandler public interface IPayoutHandler
{ {
@@ -22,6 +23,6 @@ public interface IPayoutHandler
Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethod, IClaimDestination claimDestination); Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethod, IClaimDestination claimDestination);
Dictionary<PayoutState, List<(string Action, string Text)>> GetPayoutSpecificActions(); Dictionary<PayoutState, List<(string Action, string Text)>> GetPayoutSpecificActions();
Task<StatusMessageModel> DoSpecificAction(string action, string[] payoutIds, string storeId); Task<StatusMessageModel> DoSpecificAction(string action, string[] payoutIds, string storeId);
IEnumerable<PaymentMethodId> GetSupportedPaymentMethods(); Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData);
Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds); Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds);
} }

View File

@@ -4,11 +4,13 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Configuration; using BTCPayServer.Configuration;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Security;
using BTCPayServer.Services; using BTCPayServer.Services;
using LNURL; using LNURL;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -30,6 +32,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly LightningClientFactoryService _lightningClientFactoryService; private readonly LightningClientFactoryService _lightningClientFactoryService;
private readonly IOptions<LightningNetworkOptions> _options; private readonly IOptions<LightningNetworkOptions> _options;
private readonly IAuthorizationService _authorizationService;
public LightningLikePayoutController(ApplicationDbContextFactory applicationDbContextFactory, public LightningLikePayoutController(ApplicationDbContextFactory applicationDbContextFactory,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
@@ -37,7 +40,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
IEnumerable<IPayoutHandler> payoutHandlers, IEnumerable<IPayoutHandler> payoutHandlers,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
LightningClientFactoryService lightningClientFactoryService, LightningClientFactoryService lightningClientFactoryService,
IOptions<LightningNetworkOptions> options) IOptions<LightningNetworkOptions> options, IAuthorizationService authorizationService)
{ {
_applicationDbContextFactory = applicationDbContextFactory; _applicationDbContextFactory = applicationDbContextFactory;
_userManager = userManager; _userManager = userManager;
@@ -46,6 +49,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_lightningClientFactoryService = lightningClientFactoryService; _lightningClientFactoryService = lightningClientFactoryService;
_options = options; _options = options;
_authorizationService = authorizationService;
} }
private async Task<List<PayoutData>> GetPayouts(ApplicationDbContext dbContext, PaymentMethodId pmi, private async Task<List<PayoutData>> GetPayouts(ApplicationDbContext dbContext, PaymentMethodId pmi,
@@ -145,12 +149,34 @@ namespace BTCPayServer.Data.Payouts.LightningLike
} }
} }
var authorizedForInternalNode = (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded;
foreach (var payoutDatas in payouts) foreach (var payoutDatas in payouts)
{ {
var store = payoutDatas.First().PullPaymentData.StoreData; var store = payoutDatas.First().PullPaymentData.StoreData;
var lightningSupportedPaymentMethod = store.GetSupportedPaymentMethods(_btcPayNetworkProvider) var lightningSupportedPaymentMethod = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>() .OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(method => method.PaymentId == pmi); .FirstOrDefault(method => method.PaymentId == pmi);
if (lightningSupportedPaymentMethod.IsInternalNode && !authorizedForInternalNode)
{
foreach (PayoutData payoutData in payoutDatas)
{
var blob = payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings);
results.Add(new ResultVM()
{
PayoutId = payoutData.Id,
Result = PayResult.Error,
Destination = blob.Destination,
Message =
$"You are currently using the internal lightning node for this payout's store but you are not a server admin."
});
}
continue;
}
var client = var client =
lightningSupportedPaymentMethod.CreateLightningClient(network, _options.Value, lightningSupportedPaymentMethod.CreateLightningClient(network, _options.Value,
_lightningClientFactoryService); _lightningClientFactoryService);

View File

@@ -7,8 +7,11 @@ using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services;
using BTCPayServer.Validation; using BTCPayServer.Validation;
using LNURL; using LNURL;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NBitcoin; using NBitcoin;
@@ -24,12 +27,16 @@ namespace BTCPayServer.Data.Payouts.LightningLike
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly UserService _userService;
private readonly IAuthorizationService _authorizationService;
public LightningLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider, public LightningLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider,
IHttpClientFactory httpClientFactory) IHttpClientFactory httpClientFactory, UserService userService, IAuthorizationService authorizationService)
{ {
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_userService = userService;
_authorizationService = authorizationService;
} }
public bool CanHandle(PaymentMethodId paymentMethod) public bool CanHandle(PaymentMethodId paymentMethod)
@@ -129,10 +136,28 @@ namespace BTCPayServer.Data.Payouts.LightningLike
return Task.FromResult<StatusMessageModel>(null); return Task.FromResult<StatusMessageModel>(null);
} }
public IEnumerable<PaymentMethodId> GetSupportedPaymentMethods() public async Task<IEnumerable<PaymentMethodId>> GetSupportedPaymentMethods(StoreData storeData)
{ {
return _btcPayNetworkProvider.GetAll().OfType<BTCPayNetwork>().Where(network => network.SupportLightning) var result = new List<PaymentMethodId>();
.Select(network => new PaymentMethodId(network.CryptoCode, LightningPaymentType.Instance)); var methods = storeData.GetEnabledPaymentMethods(_btcPayNetworkProvider).Where(id => id.PaymentId.PaymentType == LightningPaymentType.Instance).OfType<LightningSupportedPaymentMethod>();
foreach (LightningSupportedPaymentMethod supportedPaymentMethod in methods)
{
if (!supportedPaymentMethod.IsInternalNode)
{
result.Add(supportedPaymentMethod.PaymentId);
continue;
}
foreach (UserStore storeDataUserStore in storeData.UserStores)
{
if (!await _userService.IsAdminUser(storeDataUserStore.ApplicationUserId)) continue;
result.Add(supportedPaymentMethod.PaymentId);
break;
}
}
return result;
} }
public Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds) public Task<IActionResult> InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds)

View File

@@ -52,13 +52,11 @@ namespace BTCPayServer.Data
} }
} }
public static IEnumerable<PaymentMethodId> GetSupportedPaymentMethods( public static async Task<List<PaymentMethodId>> GetSupportedPaymentMethods(
this IEnumerable<IPayoutHandler> payoutHandlers, List<PaymentMethodId> paymentMethodIds = null) this IEnumerable<IPayoutHandler> payoutHandlers, StoreData storeData)
{ {
return payoutHandlers.SelectMany(handler => handler.GetSupportedPaymentMethods()) return (await Task.WhenAll(payoutHandlers.Select(handler => handler.GetSupportedPaymentMethods(storeData)))).SelectMany(ids => ids).ToList();
.Where(id => paymentMethodIds is null || paymentMethodIds.Contains(id) ||
//TODO: Handle this condition in a cleaner way
(id.PaymentType == LightningPaymentType.Instance && paymentMethodIds.Contains(new PaymentMethodId(id.CryptoCode, PaymentTypes.LNURLPay))));
} }
} }
} }

View File

@@ -20,14 +20,18 @@ namespace BTCPayServer.Data
} }
public static PaymentMethodId[] GetEnabledPaymentIds(this StoreData storeData, BTCPayNetworkProvider networks) public static PaymentMethodId[] GetEnabledPaymentIds(this StoreData storeData, BTCPayNetworkProvider networks)
{
return GetEnabledPaymentMethods(storeData, networks).Select(method => method.PaymentId).ToArray();
}
public static ISupportedPaymentMethod[] GetEnabledPaymentMethods(this StoreData storeData, BTCPayNetworkProvider networks)
{ {
var excludeFilter = storeData.GetStoreBlob().GetExcludedPaymentMethods(); var excludeFilter = storeData.GetStoreBlob().GetExcludedPaymentMethods();
var paymentMethodIds = storeData.GetSupportedPaymentMethods(networks) var paymentMethodIds = storeData.GetSupportedPaymentMethods(networks)
.Select(p => p.PaymentId) .Where(a => !excludeFilter.Match(a.PaymentId))
.Where(a => !excludeFilter.Match(a)) .OrderByDescending(a => a.PaymentId.CryptoCode == "BTC")
.OrderByDescending(a => a.CryptoCode == "BTC") .ThenBy(a => a.PaymentId.CryptoCode)
.ThenBy(a => a.CryptoCode) .ThenBy(a => a.PaymentId.PaymentType == PaymentTypes.LightningLike ? 1 : 0)
.ThenBy(a => a.PaymentType == PaymentTypes.LightningLike ? 1 : 0)
.ToArray(); .ToArray();
return paymentMethodIds; return paymentMethodIds;
} }

View File

@@ -14,6 +14,7 @@ namespace BTCPayServer.Models.WalletViewModels
public string PaymentMethodId { get; set; } public string PaymentMethodId { get; set; }
public List<PayoutModel> Payouts { get; set; } public List<PayoutModel> Payouts { get; set; }
public IEnumerable<PaymentMethodId> PaymentMethods { get; set; }
public PayoutState PayoutState { get; set; } public PayoutState PayoutState { get; set; }
public string PullPaymentName { get; set; } public string PullPaymentName { get; set; }

View File

@@ -37,11 +37,11 @@ namespace BTCPayServer.Services.Stores
{ {
if (userId == null) if (userId == null)
throw new ArgumentNullException(nameof(userId)); throw new ArgumentNullException(nameof(userId));
using (var ctx = _ContextFactory.CreateContext()) await using var ctx = _ContextFactory.CreateContext();
{
return (await ctx return (await ctx
.UserStore .UserStore
.Where(us => us.ApplicationUserId == userId && us.StoreDataId == storeId) .Where(us => us.ApplicationUserId == userId && us.StoreDataId == storeId)
.Include(store => store.StoreData.UserStores)
.Select(us => new .Select(us => new
{ {
Store = us.StoreData, Store = us.StoreData,
@@ -53,7 +53,6 @@ namespace BTCPayServer.Services.Stores
return us.Store; return us.Store;
}).FirstOrDefault(); }).FirstOrDefault();
} }
}
public class StoreUser public class StoreUser
{ {

View File

@@ -34,6 +34,11 @@ namespace BTCPayServer.Services
_storeRepository = storeRepository; _storeRepository = storeRepository;
} }
public async Task<bool> IsAdminUser(string userId)
{
return IsRoleAdmin(await _userManager.GetRolesAsync(new ApplicationUser(){Id = userId}));
}
public async Task<bool> IsAdminUser(ApplicationUser user) public async Task<bool> IsAdminUser(ApplicationUser user)
{ {
return IsRoleAdmin(await _userManager.GetRolesAsync(user)); return IsRoleAdmin(await _userManager.GetRolesAsync(user));

View File

@@ -10,8 +10,6 @@
ViewData["NavPartialName"] = "../Stores/_Nav"; ViewData["NavPartialName"] = "../Stores/_Nav";
ViewData.SetActivePageAndTitle(StoreNavPages.Payouts, $"Manage payouts{(string.IsNullOrEmpty(Model.PullPaymentName) ? string.Empty : " for pull payment " + Model.PullPaymentName)}", Context.GetStoreData().StoreName); ViewData.SetActivePageAndTitle(StoreNavPages.Payouts, $"Manage payouts{(string.IsNullOrEmpty(Model.PullPaymentName) ? string.Empty : " for pull payment " + Model.PullPaymentName)}", Context.GetStoreData().StoreName);
var paymentMethods = PayoutHandlers.GetSupportedPaymentMethods();
var stateActions = new List<(string Action, string Text)>(); var stateActions = new List<(string Action, string Text)>();
if (PaymentMethodId.TryParse(Model.PaymentMethodId, out var paymentMethodId)) if (PaymentMethodId.TryParse(Model.PaymentMethodId, out var paymentMethodId))
{ {
@@ -56,7 +54,7 @@
<div class="row"> <div class="row">
<div class="col-auto"> <div class="col-auto">
<ul class="nav nav-pills bg-tile mb-2" style="border-radius:4px; border: 1px solid var(--btcpay-body-border-medium)"> <ul class="nav nav-pills bg-tile mb-2" style="border-radius:4px; border: 1px solid var(--btcpay-body-border-medium)">
@foreach (var state in paymentMethods) @foreach (var state in Model.PaymentMethods)
{ {
<li class="nav-item py-0"> <li class="nav-item py-0">
<a asp-action="Payouts" asp-route-storeId="@Context.GetRouteValue("storeId")" <a asp-action="Payouts" asp-route-storeId="@Context.GetRouteValue("storeId")"