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);
return pmi;
}).ToArray();
var supported = _payoutHandlers.GetSupportedPaymentMethods().ToArray();
var supported = (await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData())).ToArray();
for (int i = 0; i < paymentMethods.Length; i++)
{
if (!supported.Contains(paymentMethods[i]))

View File

@@ -167,13 +167,15 @@ namespace BTCPayServer.Controllers
[AllowAnonymous]
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;
var invoice = await ctx.Invoices.Include(i => i.Payments)
.Include(i => i.CurrentRefund)
.Include(i => i.StoreData)
.ThenInclude(data => data.UserStores)
.Include(i => i.CurrentRefund.PullPaymentData)
.Where(i => i.Id == invoiceId)
.FirstOrDefaultAsync(cancellationToken: cancellationToken);
.FirstOrDefaultAsync(cancellationToken);
if (invoice is null)
return NotFound();
if (invoice.CurrentRefund?.PullPaymentDataId is null && GetUserId() is null)
@@ -191,7 +193,17 @@ namespace BTCPayServer.Controllers
{
var paymentMethods = invoice.GetBlob(_NetworkProvider).GetPaymentMethods();
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
.Select(p => p.GetBlob(_NetworkProvider))
.Select(p => p?.GetPaymentMethodId())

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ using BTCPayServer.Data;
using BTCPayServer.Payments;
using PayoutData = BTCPayServer.Data.PayoutData;
using Microsoft.AspNetCore.Mvc;
using StoreData = BTCPayServer.Data.StoreData;
public interface IPayoutHandler
{
@@ -22,6 +23,6 @@ public interface IPayoutHandler
Task<decimal> GetMinimumPayoutAmount(PaymentMethodId paymentMethod, IClaimDestination claimDestination);
Dictionary<PayoutState, List<(string Action, string Text)>> GetPayoutSpecificActions();
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);
}

View File

@@ -4,11 +4,13 @@ using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Configuration;
using BTCPayServer.Lightning;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Security;
using BTCPayServer.Services;
using LNURL;
using Microsoft.AspNetCore.Authorization;
@@ -30,6 +32,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly LightningClientFactoryService _lightningClientFactoryService;
private readonly IOptions<LightningNetworkOptions> _options;
private readonly IAuthorizationService _authorizationService;
public LightningLikePayoutController(ApplicationDbContextFactory applicationDbContextFactory,
UserManager<ApplicationUser> userManager,
@@ -37,7 +40,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
IEnumerable<IPayoutHandler> payoutHandlers,
BTCPayNetworkProvider btcPayNetworkProvider,
LightningClientFactoryService lightningClientFactoryService,
IOptions<LightningNetworkOptions> options)
IOptions<LightningNetworkOptions> options, IAuthorizationService authorizationService)
{
_applicationDbContextFactory = applicationDbContextFactory;
_userManager = userManager;
@@ -46,6 +49,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
_btcPayNetworkProvider = btcPayNetworkProvider;
_lightningClientFactoryService = lightningClientFactoryService;
_options = options;
_authorizationService = authorizationService;
}
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)
{
var store = payoutDatas.First().PullPaymentData.StoreData;
var lightningSupportedPaymentMethod = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>()
.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 =
lightningSupportedPaymentMethod.CreateLightningClient(network, _options.Value,
_lightningClientFactoryService);

View File

@@ -7,8 +7,11 @@ using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client.Models;
using BTCPayServer.Lightning;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services;
using BTCPayServer.Validation;
using LNURL;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
@@ -24,12 +27,16 @@ namespace BTCPayServer.Data.Payouts.LightningLike
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IHttpClientFactory _httpClientFactory;
private readonly UserService _userService;
private readonly IAuthorizationService _authorizationService;
public LightningLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider,
IHttpClientFactory httpClientFactory)
IHttpClientFactory httpClientFactory, UserService userService, IAuthorizationService authorizationService)
{
_btcPayNetworkProvider = btcPayNetworkProvider;
_httpClientFactory = httpClientFactory;
_userService = userService;
_authorizationService = authorizationService;
}
public bool CanHandle(PaymentMethodId paymentMethod)
@@ -129,10 +136,28 @@ namespace BTCPayServer.Data.Payouts.LightningLike
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)
.Select(network => new PaymentMethodId(network.CryptoCode, LightningPaymentType.Instance));
var result = new List<PaymentMethodId>();
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)

View File

@@ -52,13 +52,11 @@ namespace BTCPayServer.Data
}
}
public static IEnumerable<PaymentMethodId> GetSupportedPaymentMethods(
this IEnumerable<IPayoutHandler> payoutHandlers, List<PaymentMethodId> paymentMethodIds = null)
public static async Task<List<PaymentMethodId>> GetSupportedPaymentMethods(
this IEnumerable<IPayoutHandler> payoutHandlers, StoreData storeData)
{
return payoutHandlers.SelectMany(handler => handler.GetSupportedPaymentMethods())
.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))));
return (await Task.WhenAll(payoutHandlers.Select(handler => handler.GetSupportedPaymentMethods(storeData)))).SelectMany(ids => ids).ToList();
}
}
}

View File

@@ -20,14 +20,18 @@ namespace BTCPayServer.Data
}
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 paymentMethodIds = storeData.GetSupportedPaymentMethods(networks)
.Select(p => p.PaymentId)
.Where(a => !excludeFilter.Match(a))
.OrderByDescending(a => a.CryptoCode == "BTC")
.ThenBy(a => a.CryptoCode)
.ThenBy(a => a.PaymentType == PaymentTypes.LightningLike ? 1 : 0)
.Where(a => !excludeFilter.Match(a.PaymentId))
.OrderByDescending(a => a.PaymentId.CryptoCode == "BTC")
.ThenBy(a => a.PaymentId.CryptoCode)
.ThenBy(a => a.PaymentId.PaymentType == PaymentTypes.LightningLike ? 1 : 0)
.ToArray();
return paymentMethodIds;
}

View File

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

View File

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

View File

@@ -34,6 +34,11 @@ namespace BTCPayServer.Services
_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)
{
return IsRoleAdmin(await _userManager.GetRolesAsync(user));

View File

@@ -10,8 +10,6 @@
ViewData["NavPartialName"] = "../Stores/_Nav";
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)>();
if (PaymentMethodId.TryParse(Model.PaymentMethodId, out var paymentMethodId))
{
@@ -56,7 +54,7 @@
<div class="row">
<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)">
@foreach (var state in paymentMethods)
@foreach (var state in Model.PaymentMethods)
{
<li class="nav-item py-0">
<a asp-action="Payouts" asp-route-storeId="@Context.GetRouteValue("storeId")"