mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Decouple Pull payment from wallets (#2987)
* Decouple Pull payments from wallet * Update _Nav.cshtml * Fixes
This commit is contained in:
@@ -313,7 +313,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.Index)
|
||||
{
|
||||
Driver.FindElement(By.Id("Stores")).Click();
|
||||
GoToHome();
|
||||
Driver.WaitForAndClick(By.Id("Stores"));
|
||||
Driver.FindElement(By.Id($"update-store-{storeId}")).Click();
|
||||
|
||||
if (storeNavPage != StoreNavPages.Index)
|
||||
|
||||
@@ -995,7 +995,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
await s.FundStoreWallet(denomination: 50.0m);
|
||||
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
@@ -1003,7 +1003,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
|
||||
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP2");
|
||||
@@ -1035,13 +1035,13 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var viewPullPaymentUrl = s.Driver.Url;
|
||||
// This one should have nothing
|
||||
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||
Assert.Equal(2, payouts.Count);
|
||||
payouts[1].Click();
|
||||
Assert.Empty(s.Driver.FindElements(By.ClassName("payout")));
|
||||
// PP2 should have payouts
|
||||
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||
payouts[0].Click();
|
||||
|
||||
@@ -1061,7 +1061,7 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
Assert.Equal("payout", s.Driver.FindElement(By.ClassName("transactionLabel")).Text);
|
||||
|
||||
s.GoToWallet(navPages: WalletsNavPages.Payouts);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
||||
var x = s.Driver.PageSource;
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.InProgress}-view")).Click();
|
||||
ReadOnlyCollection<IWebElement> txs;
|
||||
@@ -1103,7 +1103,7 @@ namespace BTCPayServer.Tests
|
||||
var newStore = s.CreateNewStore();
|
||||
s.GenerateWallet("BTC", "", true, true);
|
||||
var newWalletId = new WalletId(newStore.storeId, "BTC");
|
||||
s.GoToWallet(newWalletId, WalletsNavPages.PullPayments);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("External Test");
|
||||
@@ -1120,7 +1120,7 @@ namespace BTCPayServer.Tests
|
||||
s.FindAlertMessage();
|
||||
|
||||
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
|
||||
s.GoToWallet(newWalletId, WalletsNavPages.Payouts);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-view")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click();
|
||||
@@ -1128,7 +1128,7 @@ namespace BTCPayServer.Tests
|
||||
s.FindAlertMessage();
|
||||
var tx =await s.Server.ExplorerNode.SendToAddressAsync(address, Money.FromUnit(0.001m, MoneyUnit.BTC));
|
||||
|
||||
s.GoToWallet(newWalletId, WalletsNavPages.Payouts);
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
||||
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-view")).Click();
|
||||
Assert.Contains(PayoutState.AwaitingPayment.GetStateString(), s.Driver.PageSource);
|
||||
@@ -1146,8 +1146,8 @@ namespace BTCPayServer.Tests
|
||||
s.AddLightningNode("BTC");
|
||||
//Currently an onchain wallet is required to use the Lightning payouts feature..
|
||||
s.GenerateWallet("BTC", "", true, true);
|
||||
newWalletId = new WalletId(newStore.storeId, "BTC");
|
||||
s.GoToWallet(newWalletId, WalletsNavPages.PullPayments);
|
||||
|
||||
s.GoToStore(newStore.storeId, StoreNavPages.PullPayments);
|
||||
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
|
||||
@@ -1188,7 +1188,8 @@ namespace BTCPayServer.Tests
|
||||
s.FindAlertMessage();
|
||||
|
||||
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
|
||||
s.GoToWallet(newWalletId, WalletsNavPages.Payouts);
|
||||
|
||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike )}-view")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-view")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click();
|
||||
@@ -1200,7 +1201,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.CssSelector("#pay-invoices-form")).Submit();
|
||||
//lightning config in tests is very unstable so we can just go ahead and handle it as both
|
||||
s.FindAlertMessage(new []{StatusMessageModel.StatusSeverity.Error, StatusMessageModel.StatusSeverity.Success});
|
||||
s.GoToWallet(newWalletId, WalletsNavPages.Payouts);
|
||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike )}-view")).Click();
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,15 @@
|
||||
<None Remove="Build\**" />
|
||||
<None Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<None Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<Content Update="Views\StorePullPayments\NewPullPayment.cshtml">
|
||||
<Pack>false</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\StorePullPayments\PullPayments.cshtml">
|
||||
<Pack>false</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\StorePullPayments\Payouts.cshtml">
|
||||
<Pack>false</Pack>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Currencies.txt" />
|
||||
@@ -197,12 +206,6 @@
|
||||
<Content Update="Views\Wallets\ListWallets.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\NewPullPayment.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\Payouts.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletPSBTCombine.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
@@ -218,9 +221,6 @@
|
||||
<Content Update="Views\Wallets\WalletSendVault.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\PullPayments.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletTransactions.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
|
||||
@@ -1,39 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
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.Common;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Views;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using PayoutData = BTCPayServer.Data.PayoutData;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class WalletsController
|
||||
[Route("stores/{storeId}/pull-payments")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[AutoValidateAntiforgeryToken]
|
||||
public class StorePullPaymentsController: Controller
|
||||
{
|
||||
[HttpGet("{walletId}/pull-payments/new")]
|
||||
public IActionResult NewPullPayment([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId)
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly PullPaymentHostedService _pullPaymentService;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
||||
|
||||
public StoreData CurrentStore
|
||||
{
|
||||
|
||||
if (GetDerivationSchemeSettings(walletId) == null)
|
||||
get
|
||||
{
|
||||
return HttpContext.GetStoreData();
|
||||
}
|
||||
}
|
||||
public StorePullPaymentsController(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
ApplicationDbContextFactory dbContextFactory,
|
||||
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
_currencyNameTable = currencyNameTable;
|
||||
_pullPaymentService = pullPaymentHostedService;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_jsonSerializerSettings = jsonSerializerSettings;
|
||||
}
|
||||
|
||||
[HttpGet("new")]
|
||||
public IActionResult NewPullPayment(string storeId)
|
||||
{
|
||||
if (CurrentStore is null)
|
||||
return NotFound();
|
||||
var storeMethods = CurrentStore.GetSupportedPaymentMethods(NetworkProvider).Select(method => method.PaymentId).ToList();
|
||||
var storeMethods = CurrentStore.GetSupportedPaymentMethods(_btcPayNetworkProvider).Select(method => method.PaymentId).ToList();
|
||||
var paymentMethodOptions = _payoutHandlers.GetSupportedPaymentMethods(storeMethods);
|
||||
return View(new NewPullPaymentModel
|
||||
{
|
||||
@@ -45,14 +76,13 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{walletId}/pull-payments/new")]
|
||||
public async Task<IActionResult> NewPullPayment([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, NewPullPaymentModel model)
|
||||
[HttpPost("new")]
|
||||
public async Task<IActionResult> NewPullPayment(string storeId, NewPullPaymentModel model)
|
||||
{
|
||||
if (GetDerivationSchemeSettings(walletId) == null)
|
||||
if (CurrentStore is null)
|
||||
return NotFound();
|
||||
|
||||
var storeMethods = CurrentStore.GetSupportedPaymentMethods(NetworkProvider).Select(method => method.PaymentId).ToList();
|
||||
var storeMethods = CurrentStore.GetSupportedPaymentMethods(_btcPayNetworkProvider).Select(method => method.PaymentId).ToList();
|
||||
var paymentMethodOptions = _payoutHandlers.GetSupportedPaymentMethods(storeMethods);
|
||||
model.PaymentMethodItems =
|
||||
paymentMethodOptions.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true));
|
||||
@@ -62,7 +92,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.PaymentMethods), "You need at least one payment method");
|
||||
}
|
||||
if (_currencyTable.GetCurrencyData(model.Currency, false) is null)
|
||||
if (_currencyNameTable.GetCurrencyData(model.Currency, false) is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.Currency), "Invalid currency");
|
||||
}
|
||||
@@ -87,7 +117,7 @@ namespace BTCPayServer.Controllers
|
||||
Name = model.Name,
|
||||
Amount = model.Amount,
|
||||
Currency = model.Currency,
|
||||
StoreId = walletId.StoreId,
|
||||
StoreId = storeId,
|
||||
PaymentMethodIds = selectedPaymentMethodIds,
|
||||
EmbeddedCSS = model.EmbeddedCSS,
|
||||
CustomCSSLink = model.CustomCSSLink
|
||||
@@ -97,17 +127,14 @@ namespace BTCPayServer.Controllers
|
||||
Message = "Pull payment request created",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
return RedirectToAction(nameof(PullPayments), new { walletId = walletId.ToString() });
|
||||
return RedirectToAction(nameof(PullPayments), new { storeId = storeId });
|
||||
}
|
||||
|
||||
[HttpGet("{walletId}/pull-payments")]
|
||||
public async Task<IActionResult> PullPayments(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId)
|
||||
[HttpGet("")]
|
||||
public async Task<IActionResult> PullPayments(string storeId)
|
||||
{
|
||||
using var ctx = this._dbContextFactory.CreateContext();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var storeId = walletId.StoreId;
|
||||
var pps = await ctx.PullPayments.Where(p => p.StoreId == storeId && !p.Archived)
|
||||
.OrderByDescending(p => p.StartDate)
|
||||
.Select(o => new
|
||||
@@ -120,8 +147,7 @@ namespace BTCPayServer.Controllers
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
var vm = new PullPaymentsModel
|
||||
{ HasDerivationSchemeSettings = GetDerivationSchemeSettings(walletId) != null };
|
||||
var vm = new PullPaymentsModel();
|
||||
|
||||
foreach (var o in pps)
|
||||
{
|
||||
@@ -131,8 +157,8 @@ namespace BTCPayServer.Controllers
|
||||
var totalAwaiting = o.Awaiting.Where(o => o.IsInPeriod(pp, now))
|
||||
.Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum();
|
||||
var ppBlob = pp.GetBlob();
|
||||
var ni = _currencyTable.GetCurrencyData(ppBlob.Currency, true);
|
||||
var nfi = _currencyTable.GetNumberFormatInfo(ppBlob.Currency, true);
|
||||
var ni = _currencyNameTable.GetCurrencyData(ppBlob.Currency, true);
|
||||
var nfi = _currencyNameTable.GetNumberFormatInfo(ppBlob.Currency, true);
|
||||
var period = pp.GetPeriod(now);
|
||||
vm.PullPayments.Add(new PullPaymentsModel.PullPaymentModel()
|
||||
{
|
||||
@@ -146,7 +172,7 @@ namespace BTCPayServer.Controllers
|
||||
AwaitingPercent = (int)(totalAwaiting / ppBlob.Limit * 100m),
|
||||
Awaiting = totalAwaiting.RoundToSignificant(ni.Divisibility).ToString("C", nfi),
|
||||
Completed = totalCompleted.RoundToSignificant(ni.Divisibility).ToString("C", nfi),
|
||||
Limit = _currencyTable.DisplayFormatCurrency(ppBlob.Limit, ppBlob.Currency),
|
||||
Limit = _currencyNameTable.DisplayFormatCurrency(ppBlob.Limit, ppBlob.Currency),
|
||||
ResetIn = period?.End is DateTimeOffset nr ? ZeroIfNegative(nr - now).TimeString() : null,
|
||||
EndIn = pp.EndDate is DateTimeOffset end ? ZeroIfNegative(end - now).TimeString() : null
|
||||
}
|
||||
@@ -161,19 +187,15 @@ namespace BTCPayServer.Controllers
|
||||
return time;
|
||||
}
|
||||
|
||||
[HttpGet("{walletId}/pull-payments/{pullPaymentId}/archive")]
|
||||
public IActionResult ArchivePullPayment(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId,
|
||||
[HttpGet("{pullPaymentId}/archive")]
|
||||
public IActionResult ArchivePullPayment(string storeId,
|
||||
string pullPaymentId)
|
||||
{
|
||||
return View("Confirm", new ConfirmModel("Archive pull payment", "Do you really want to archive the pull payment?", "Archive"));
|
||||
}
|
||||
|
||||
[HttpPost("{walletId}/pull-payments/{pullPaymentId}/archive")]
|
||||
public async Task<IActionResult> ArchivePullPaymentPost(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId,
|
||||
[HttpPost("{pullPaymentId}/archive")]
|
||||
public async Task<IActionResult> ArchivePullPaymentPost(string storeId,
|
||||
string pullPaymentId)
|
||||
{
|
||||
await _pullPaymentService.Cancel(new HostedServices.PullPaymentHostedService.CancelRequest(pullPaymentId));
|
||||
@@ -182,18 +204,15 @@ namespace BTCPayServer.Controllers
|
||||
Message = "Pull payment archived",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
return RedirectToAction(nameof(PullPayments), new { walletId = walletId.ToString() });
|
||||
return RedirectToAction(nameof(PullPayments), new { storeId = storeId });
|
||||
}
|
||||
|
||||
[HttpPost("{walletId}/payouts")]
|
||||
[HttpPost("payouts")]
|
||||
public async Task<IActionResult> PayoutsPost(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, PayoutsModel vm, CancellationToken cancellationToken)
|
||||
string storeId, PayoutsModel vm, CancellationToken cancellationToken)
|
||||
{
|
||||
if (vm is null || GetDerivationSchemeSettings(walletId) == null)
|
||||
if (vm is null)
|
||||
return NotFound();
|
||||
|
||||
var storeId = walletId.StoreId;
|
||||
var paymentMethodId = PaymentMethodId.Parse(vm.PaymentMethodId);
|
||||
var handler = _payoutHandlers
|
||||
.FindPayoutHandler(paymentMethodId);
|
||||
@@ -208,7 +227,7 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts), new
|
||||
{
|
||||
walletId = walletId.ToString(),
|
||||
storeId = storeId,
|
||||
pullPaymentId = vm.PullPaymentId,
|
||||
paymentMethodId = paymentMethodId.ToString()
|
||||
});
|
||||
@@ -216,7 +235,7 @@ namespace BTCPayServer.Controllers
|
||||
var command = vm.Command.Substring(vm.Command.IndexOf('-', StringComparison.InvariantCulture) + 1);
|
||||
if (handler != null)
|
||||
{
|
||||
var result = await handler.DoSpecificAction(command, payoutIds, walletId.StoreId);
|
||||
var result = await handler.DoSpecificAction(command, payoutIds, storeId);
|
||||
if (result != null)
|
||||
{
|
||||
TempData.SetStatusMessageModel(result);
|
||||
@@ -315,7 +334,7 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
return RedirectToAction(nameof(Payouts), new
|
||||
{
|
||||
walletId = walletId.ToString(),
|
||||
storeId = storeId,
|
||||
pullPaymentId = vm.PullPaymentId,
|
||||
paymentMethodId = paymentMethodId.ToString()
|
||||
});
|
||||
@@ -342,7 +361,7 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(Payouts),
|
||||
new
|
||||
{
|
||||
walletId = walletId.ToString(),
|
||||
storeId = storeId,
|
||||
pullPaymentId = vm.PullPaymentId,
|
||||
paymentMethodId = paymentMethodId.ToString()
|
||||
});
|
||||
@@ -363,15 +382,14 @@ namespace BTCPayServer.Controllers
|
||||
return payouts;
|
||||
}
|
||||
|
||||
[HttpGet("{walletId}/payouts")]
|
||||
[HttpGet("payouts")]
|
||||
public async Task<IActionResult> Payouts(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, string pullPaymentId, string paymentMethodId, PayoutState payoutState,
|
||||
string storeId, string pullPaymentId, string paymentMethodId, PayoutState payoutState,
|
||||
int skip = 0, int count = 50)
|
||||
{
|
||||
var vm = this.ParseListQuery(new PayoutsModel
|
||||
{
|
||||
PaymentMethodId = paymentMethodId?? new PaymentMethodId(walletId.CryptoCode, PaymentTypes.BTCLike).ToString(),
|
||||
PaymentMethodId = paymentMethodId?? _payoutHandlers.GetSupportedPaymentMethods().First().ToString(),
|
||||
PullPaymentId = pullPaymentId,
|
||||
PayoutState = payoutState,
|
||||
Skip = skip,
|
||||
@@ -379,13 +397,13 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
vm.Payouts = new List<PayoutsModel.PayoutModel>();
|
||||
await using var ctx = _dbContextFactory.CreateContext();
|
||||
var storeId = walletId.StoreId;
|
||||
var payoutRequest = ctx.Payouts.Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived);
|
||||
if (pullPaymentId != null)
|
||||
{
|
||||
payoutRequest = payoutRequest.Where(p => p.PullPaymentDataId == vm.PullPaymentId);
|
||||
vm.PullPaymentName = (await ctx.PullPayments.FindAsync(pullPaymentId)).GetBlob().Name;
|
||||
}
|
||||
|
||||
if (vm.PaymentMethodId != null)
|
||||
{
|
||||
var pmiStr = vm.PaymentMethodId;
|
||||
@@ -425,7 +443,7 @@ namespace BTCPayServer.Controllers
|
||||
PullPaymentName = ppBlob.Name ?? item.PullPayment.Id,
|
||||
Date = item.Payout.Date,
|
||||
PayoutId = item.Payout.Id,
|
||||
Amount = _currencyTable.DisplayFormatCurrency(payoutBlob.Amount, ppBlob.Currency),
|
||||
Amount = _currencyNameTable.DisplayFormatCurrency(payoutBlob.Amount, ppBlob.Currency),
|
||||
Destination = payoutBlob.Destination
|
||||
};
|
||||
var handler = _payoutHandlers
|
||||
@@ -63,12 +63,13 @@ namespace Microsoft.AspNetCore.Mvc
|
||||
scheme, host, pathbase);
|
||||
}
|
||||
|
||||
public static string PayoutLink(this LinkGenerator urlHelper, string walletId,string pullPaymentId, string scheme, HostString host, string pathbase)
|
||||
public static string PayoutLink(this LinkGenerator urlHelper, string walletIdOrStoreId,string pullPaymentId, string scheme, HostString host, string pathbase)
|
||||
{
|
||||
WalletId.TryParse(walletIdOrStoreId, out var wallet);
|
||||
return urlHelper.GetUriByAction(
|
||||
action: nameof(WalletsController.Payouts),
|
||||
controller: "Wallets",
|
||||
values: new {walletId, pullPaymentId},
|
||||
action: nameof(StorePullPaymentsController.Payouts),
|
||||
controller: "StorePullPayments",
|
||||
values: new {storeId= wallet?.StoreId?? walletIdOrStoreId , pullPaymentId},
|
||||
scheme, host, pathbase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,6 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
}
|
||||
|
||||
public List<PullPaymentModel> PullPayments { get; set; } = new List<PullPaymentModel>();
|
||||
|
||||
public bool HasDerivationSchemeSettings { get; set; }
|
||||
}
|
||||
|
||||
public class NewPullPaymentModel
|
||||
|
||||
@@ -2,6 +2,7 @@ using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace BTCPayServer.Services.Notifications.Blobs
|
||||
@@ -36,11 +37,12 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
||||
{
|
||||
vm.Body =
|
||||
"A payment that was made to an approved payout by an external wallet is waiting for your confirmation.";
|
||||
vm.ActionLink = _linkGenerator.GetPathByAction(nameof(WalletsController.Payouts),
|
||||
"Wallets",
|
||||
vm.ActionLink = _linkGenerator.GetPathByAction(nameof(StorePullPaymentsController.Payouts),
|
||||
"StorePullPayments",
|
||||
new
|
||||
{
|
||||
walletId = new WalletId(notification.StoreId, notification.PaymentMethod),
|
||||
storeId = notification.StoreId,
|
||||
paymentMethodId = notification.PaymentMethod,
|
||||
payoutState = PayoutState.AwaitingPayment
|
||||
}, _options.RootPath);
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
||||
protected override void FillViewModel(PayoutNotification notification, NotificationViewModel vm)
|
||||
{
|
||||
vm.Body = "A new payout is awaiting for approval";
|
||||
vm.ActionLink = _linkGenerator.GetPathByAction(nameof(WalletsController.Payouts),
|
||||
"Wallets",
|
||||
new {walletId = new WalletId(notification.StoreId, notification.PaymentMethod)}, _options.RootPath);
|
||||
vm.ActionLink = _linkGenerator.GetPathByAction(nameof(StorePullPaymentsController.Payouts),
|
||||
"StorePullPayments",
|
||||
new {storeId = notification.StoreId, paymentMethodId = notification.PaymentMethod }, _options.RootPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
@model NewPullPaymentModel
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Views.Stores
|
||||
@model BTCPayServer.Models.WalletViewModels.NewPullPaymentModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.PullPayments, "New pull payment", Context.GetStoreData().StoreName);
|
||||
ViewData["NavPartialName"] = "../Stores/_Nav";
|
||||
ViewData.SetActivePageAndTitle(StoreNavPages.PullPayments, "New pull payment", Context.GetStoreData().StoreName);
|
||||
}
|
||||
|
||||
<style type="text/css">
|
||||
@@ -1,19 +1,26 @@
|
||||
@using BTCPayServer.Client.Models
|
||||
@using BTCPayServer.Payments
|
||||
@model PayoutsModel
|
||||
@using BTCPayServer.Views.Stores
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@model BTCPayServer.Models.WalletViewModels.PayoutsModel
|
||||
|
||||
@inject IEnumerable<IPayoutHandler> PayoutHandlers;
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.Payouts, $"Manage payouts{(string.IsNullOrEmpty(Model.PullPaymentName) ? string.Empty : " for pull payment " + Model.PullPaymentName)}", Context.GetStoreData().StoreName);
|
||||
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)>();
|
||||
var payoutHandler = PayoutHandlers.FindPayoutHandler(PaymentMethodId.Parse(Model.PaymentMethodId));
|
||||
if (payoutHandler is null)
|
||||
return;
|
||||
stateActions.AddRange(payoutHandler.GetPayoutSpecificActions().Where(pair => pair.Key == Model.PayoutState).SelectMany(pair => pair.Value));
|
||||
if (PaymentMethodId.TryParse(Model.PaymentMethodId, out var paymentMethodId))
|
||||
{
|
||||
var payoutHandler = PayoutHandlers.FindPayoutHandler(paymentMethodId);
|
||||
if (payoutHandler is null)
|
||||
return;
|
||||
stateActions.AddRange(payoutHandler.GetPayoutSpecificActions().Where(pair => pair.Key == Model.PayoutState).SelectMany(pair => pair.Value));
|
||||
|
||||
}
|
||||
switch (Model.PayoutState)
|
||||
{
|
||||
case PayoutState.AwaitingApproval:
|
||||
@@ -27,6 +34,7 @@
|
||||
stateActions.Add(("mark-paid", "Mark selected payouts as already paid"));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@section PageFootContent {
|
||||
@@ -51,7 +59,7 @@
|
||||
@foreach (var state in paymentMethods)
|
||||
{
|
||||
<li class="nav-item py-0">
|
||||
<a asp-action="Payouts" asp-route-walletId="@Context.GetRouteValue("walletId")"
|
||||
<a asp-action="Payouts" asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-payoutState="@Model.PayoutState"
|
||||
asp-route-paymentMethodId="@state.ToString()"
|
||||
asp-route-pullPaymentId="@Model.PullPaymentId"
|
||||
@@ -69,7 +77,7 @@
|
||||
<li class="nav-item py-0">
|
||||
<a id="@state.Key-view"
|
||||
asp-action="Payouts"
|
||||
asp-route-walletId="@Context.GetRouteValue("walletId")"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-payoutState="@state.Key"
|
||||
asp-route-pullPaymentId="@Model.PullPaymentId"
|
||||
asp-route-paymentMethodId="@Model.PaymentMethodId"
|
||||
@@ -1,7 +1,10 @@
|
||||
@model PullPaymentsModel
|
||||
@using BTCPayServer.Views.Stores
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@model BTCPayServer.Models.WalletViewModels.PullPaymentsModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.PullPayments, "Pull payments", Context.GetStoreData().StoreName);
|
||||
ViewData["NavPartialName"] = "../Stores/_Nav";
|
||||
ViewData.SetActivePageAndTitle(StoreNavPages.PullPayments, "Pull payments", Context.GetStoreData().StoreName);
|
||||
}
|
||||
|
||||
@section PageHeadContent {
|
||||
@@ -21,12 +24,9 @@
|
||||
</a>
|
||||
</small>
|
||||
</h4>
|
||||
@if (Model.HasDerivationSchemeSettings)
|
||||
{
|
||||
<a asp-action="NewPullPayment" asp-route-walletId="@Context.GetRouteValue("walletId")" class="btn btn-primary" role="button" id="NewPullPayment">
|
||||
<span class="fa fa-plus"></span> Create a new pull payment
|
||||
</a>
|
||||
}
|
||||
<a asp-action="NewPullPayment" asp-route-storeId="@Context.GetRouteValue("storeId")" class="btn btn-primary" role="button" id="NewPullPayment">
|
||||
<span class="fa fa-plus"></span> Create a new pull payment
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (Model.PullPayments.Any())
|
||||
@@ -83,10 +83,10 @@
|
||||
asp-controller="PullPayment"
|
||||
asp-route-pullPaymentId="@pp.Id">View</a> -
|
||||
<a class="pp-payout" asp-action="Payouts"
|
||||
asp-route-walletId="@Context.GetRouteValue("walletId")"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-pullPaymentId="@pp.Id">Payouts</a> -
|
||||
<a asp-action="ArchivePullPayment"
|
||||
asp-route-walletId="@Context.GetRouteValue("walletId")"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-pullPaymentId="@pp.Id"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#ConfirmModal"
|
||||
@@ -2,6 +2,8 @@ namespace BTCPayServer.Views.Stores
|
||||
{
|
||||
public enum StoreNavPages
|
||||
{
|
||||
Index, Create, Rates, Checkout, Tokens, Users, PayButton, Integrations, Wallet, Webhooks, ActivePage
|
||||
Index, Create, Rates, Checkout, Tokens, Users, PayButton, Integrations, Wallet, Webhooks, ActivePage,
|
||||
PullPayments,
|
||||
Payouts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,7 @@
|
||||
<a id="@(nameof(StoreNavPages.PayButton))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Pay Button</a>
|
||||
<a id="@(nameof(StoreNavPages.Integrations))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Integrations)" asp-controller="Stores" asp-action="Integrations" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Integrations</a>
|
||||
<a id="@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Webhooks)" asp-controller="Stores" asp-action="Webhooks" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Webhooks</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(StoreNavPages.PullPayments)" asp-action="PullPayments" asp-controller="StorePullPayments" asp-route-storeId="@this.Context.GetRouteValue("storeId")" id="PullPayments">Pull payments</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" asp-action="Payouts" asp-controller="StorePullPayments" asp-route-storeId="@this.Context.GetRouteValue("storeId")" id="Payouts">Payouts</a>
|
||||
<vc:ui-extension-point location="store-nav" model="@Model" />
|
||||
</nav>
|
||||
|
||||
@@ -7,8 +7,6 @@ namespace BTCPayServer.Views.Wallets
|
||||
Transactions,
|
||||
Rescan,
|
||||
PSBT,
|
||||
PullPayments,
|
||||
Payouts,
|
||||
Settings,
|
||||
Receive
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@inject SignInManager<ApplicationUser> SignInManager
|
||||
@inject SignInManager<ApplicationUser> SignInManager
|
||||
@inject BTCPayNetworkProvider BtcPayNetworkProvider
|
||||
@{
|
||||
var wallet = WalletId.Parse(Context.GetRouteValue("walletId").ToString());
|
||||
@@ -12,8 +12,6 @@
|
||||
}
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Receive)" asp-action="WalletReceive" asp-route-walletId="@Context.GetRouteValue("walletId")" id="WalletReceive">Receive</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Rescan)" asp-action="WalletRescan" asp-route-walletId="@Context.GetRouteValue("walletId")" id="WalletRescan">Rescan</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.PullPayments)" asp-action="PullPayments" asp-route-walletId="@Context.GetRouteValue("walletId")" id="WalletPullPayments">Pull payments</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Payouts)" asp-action="Payouts" asp-route-walletId="@Context.GetRouteValue("walletId")" id="WalletPayouts">Payouts</a>
|
||||
@if (!network.ReadonlyWallet)
|
||||
{
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.PSBT)" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")">PSBT</a>
|
||||
|
||||
Reference in New Issue
Block a user