Introduce archive pull payment permission and add Show QR code in view pull payment view (#5274)

* Introduce archive pull payment permission

* Add show qr option on pull payments

* Fix test

* update docs

* fix test

* Minor UI updates

* Update wording

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
Andrew Camilleri 2023-09-22 10:24:53 +02:00 committed by GitHub
parent eaeb7021d5
commit 33198d693d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 131 additions and 99 deletions

View file

@ -33,6 +33,7 @@ namespace BTCPayServer.Client
public const string CanManageUsers = "btcpay.server.canmanageusers"; public const string CanManageUsers = "btcpay.server.canmanageusers";
public const string CanDeleteUser = "btcpay.user.candeleteuser"; public const string CanDeleteUser = "btcpay.user.candeleteuser";
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments"; public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
public const string CanArchivePullPayments = "btcpay.store.canarchivepullpayments";
public const string CanCreatePullPayments = "btcpay.store.cancreatepullpayments"; public const string CanCreatePullPayments = "btcpay.store.cancreatepullpayments";
public const string CanCreateNonApprovedPullPayments = "btcpay.store.cancreatenonapprovedpullpayments"; public const string CanCreateNonApprovedPullPayments = "btcpay.store.cancreatenonapprovedpullpayments";
public const string CanViewCustodianAccounts = "btcpay.store.canviewcustodianaccounts"; public const string CanViewCustodianAccounts = "btcpay.store.canviewcustodianaccounts";
@ -69,6 +70,7 @@ namespace BTCPayServer.Client
yield return CanViewLightningInvoiceInStore; yield return CanViewLightningInvoiceInStore;
yield return CanCreateLightningInvoiceInStore; yield return CanCreateLightningInvoiceInStore;
yield return CanManagePullPayments; yield return CanManagePullPayments;
yield return CanArchivePullPayments;
yield return CanCreatePullPayments; yield return CanCreatePullPayments;
yield return CanCreateNonApprovedPullPayments; yield return CanCreateNonApprovedPullPayments;
yield return CanViewCustodianAccounts; yield return CanViewCustodianAccounts;
@ -253,7 +255,7 @@ namespace BTCPayServer.Client
Policies.CanUseLightningNodeInStore); Policies.CanUseLightningNodeInStore);
PolicyHasChild(policyMap,Policies.CanManageUsers, Policies.CanCreateUser); PolicyHasChild(policyMap,Policies.CanManageUsers, Policies.CanCreateUser);
PolicyHasChild(policyMap,Policies.CanManagePullPayments, Policies.CanCreatePullPayments); PolicyHasChild(policyMap,Policies.CanManagePullPayments, Policies.CanCreatePullPayments, Policies.CanArchivePullPayments);
PolicyHasChild(policyMap,Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments); PolicyHasChild(policyMap,Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments);
PolicyHasChild(policyMap,Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests); PolicyHasChild(policyMap,Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests);
PolicyHasChild(policyMap,Policies.CanModifyProfile, Policies.CanViewProfile); PolicyHasChild(policyMap,Policies.CanModifyProfile, Policies.CanViewProfile);

View file

@ -885,7 +885,7 @@ namespace BTCPayServer.Tests
TestLogs.LogInformation("Can't archive without knowing the walletId"); TestLogs.LogInformation("Can't archive without knowing the walletId");
var ex = await AssertAPIError("missing-permission", async () => await client.ArchivePullPayment("lol", result.Id)); var ex = await AssertAPIError("missing-permission", async () => await client.ArchivePullPayment("lol", result.Id));
Assert.Equal("btcpay.store.canmanagepullpayments", ((GreenfieldPermissionAPIError)ex.APIError).MissingPermission); Assert.Equal("btcpay.store.canarchivepullpayments", ((GreenfieldPermissionAPIError)ex.APIError).MissingPermission);
TestLogs.LogInformation("Can't archive without permission"); TestLogs.LogInformation("Can't archive without permission");
await AssertAPIError("unauthenticated", async () => await unauthenticated.ArchivePullPayment(storeId, result.Id)); await AssertAPIError("unauthenticated", async () => await unauthenticated.ArchivePullPayment(storeId, result.Id));
await client.ArchivePullPayment(storeId, result.Id); await client.ArchivePullPayment(storeId, result.Id);

View file

@ -38,6 +38,7 @@ using OpenQA.Selenium.Support.Extensions;
using OpenQA.Selenium.Support.UI; using OpenQA.Selenium.Support.UI;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
using Xunit.Sdk;
namespace BTCPayServer.Tests namespace BTCPayServer.Tests
{ {
@ -926,7 +927,8 @@ namespace BTCPayServer.Tests
Policies.CanModifyStoreSettings, Policies.CanModifyStoreSettings,
Policies.CanCreateNonApprovedPullPayments, Policies.CanCreateNonApprovedPullPayments,
Policies.CanCreatePullPayments, Policies.CanCreatePullPayments,
Policies.CanManagePullPayments Policies.CanManagePullPayments,
Policies.CanArchivePullPayments,
}); });
AssertPermissions(pageSource, false, AssertPermissions(pageSource, false,
new[] new[]
@ -1848,6 +1850,7 @@ namespace BTCPayServer.Tests
TestUtils.Eventually(() => TestUtils.Eventually(() =>
{ {
s.Driver.Navigate().Refresh(); s.Driver.Navigate().Refresh();
s.Driver.WaitWalletTransactionsLoaded();
Assert.Contains("transaction-label", s.Driver.PageSource); Assert.Contains("transaction-label", s.Driver.PageSource);
var labels = s.Driver.FindElements(By.CssSelector("#WalletTransactionsList tr:first-child div.transaction-label")); var labels = s.Driver.FindElements(By.CssSelector("#WalletTransactionsList tr:first-child div.transaction-label"));
Assert.Equal(2, labels.Count); Assert.Equal(2, labels.Count);
@ -2017,10 +2020,7 @@ namespace BTCPayServer.Tests
Assert.Contains(bolt, s.Driver.PageSource); Assert.Contains(bolt, s.Driver.PageSource);
} }
//auto-approve pull payments //auto-approve pull payments
s.GoToStore(StoreNavPages.PullPayments); s.GoToStore(StoreNavPages.PullPayments);
s.Driver.FindElement(By.Id("NewPullPayment")).Click(); s.Driver.FindElement(By.Id("NewPullPayment")).Click();
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1"); s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
@ -2050,6 +2050,8 @@ namespace BTCPayServer.Tests
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success); s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
s.Driver.FindElement(By.LinkText("View")).Click(); s.Driver.FindElement(By.LinkText("View")).Click();
s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click(); s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click();
s.Driver.WaitForElement(By.Id("qr-code-data-input"));
var lnurl = new Uri(LNURL.LNURL.Parse(s.Driver.FindElement(By.Id("qr-code-data-input")).GetAttribute("value"), out _).ToString().Replace("https", "http")); var lnurl = new Uri(LNURL.LNURL.Parse(s.Driver.FindElement(By.Id("qr-code-data-input")).GetAttribute("value"), out _).ToString().Replace("https", "http"));
s.Driver.FindElement(By.CssSelector("button[data-bs-dismiss='modal']")).Click(); s.Driver.FindElement(By.CssSelector("button[data-bs-dismiss='modal']")).Click();
var info = Assert.IsType<LNURLWithdrawRequest>(await LNURL.LNURL.FetchInformation(lnurl, s.Server.PayTester.HttpClient)); var info = Assert.IsType<LNURLWithdrawRequest>(await LNURL.LNURL.FetchInformation(lnurl, s.Server.PayTester.HttpClient));

View file

@ -442,7 +442,7 @@ namespace BTCPayServer.Controllers.Greenfield
} }
[HttpDelete("~/api/v1/stores/{storeId}/pull-payments/{pullPaymentId}")] [HttpDelete("~/api/v1/stores/{storeId}/pull-payments/{pullPaymentId}")]
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [Authorize(Policy = Policies.CanArchivePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> ArchivePullPayment(string storeId, string pullPaymentId) public async Task<IActionResult> ArchivePullPayment(string storeId, string pullPaymentId)
{ {
using var ctx = _dbContextFactory.CreateContext(); using var ctx = _dbContextFactory.CreateContext();

View file

@ -544,6 +544,8 @@ namespace BTCPayServer.Controllers
{$"{Policies.CanViewPaymentRequests}:", ("View your payment requests", "Allows viewing the selected stores' payment requests.")}, {$"{Policies.CanViewPaymentRequests}:", ("View your payment requests", "Allows viewing the selected stores' payment requests.")},
{Policies.CanManagePullPayments, ("Manage your pull payments", "Allows viewing, modifying, deleting and creating pull payments on all your stores.")}, {Policies.CanManagePullPayments, ("Manage your pull payments", "Allows viewing, modifying, deleting and creating pull payments on all your stores.")},
{$"{Policies.CanManagePullPayments}:", ("Manage selected stores' pull payments", "Allows viewing, modifying, deleting and creating pull payments on the selected stores.")}, {$"{Policies.CanManagePullPayments}:", ("Manage selected stores' pull payments", "Allows viewing, modifying, deleting and creating pull payments on the selected stores.")},
{Policies.CanArchivePullPayments, ("Archive your pull payments", "Allows deleting pull payments on all your stores.")},
{$"{Policies.CanArchivePullPayments}:", ("Archive selected stores' pull payments", "Allows deleting pull payments on the selected stores.")},
{Policies.CanCreatePullPayments, ("Create pull payments", "Allows creating pull payments on all your stores.")}, {Policies.CanCreatePullPayments, ("Create pull payments", "Allows creating pull payments on all your stores.")},
{$"{Policies.CanCreatePullPayments}:", ("Create pull payments in selected stores", "Allows creating pull payments on the selected stores.")}, {$"{Policies.CanCreatePullPayments}:", ("Create pull payments in selected stores", "Allows creating pull payments on the selected stores.")},
{Policies.CanCreateNonApprovedPullPayments, ("Create non-approved pull payments", "Allows creating pull payments without automatic approval on all your stores.")}, {Policies.CanCreateNonApprovedPullPayments, ("Create non-approved pull payments", "Allows creating pull payments without automatic approval on all your stores.")},

View file

@ -256,7 +256,7 @@ namespace BTCPayServer.Controllers
} }
[HttpGet("stores/{storeId}/pull-payments/{pullPaymentId}/archive")] [HttpGet("stores/{storeId}/pull-payments/{pullPaymentId}/archive")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanArchivePullPayments, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult ArchivePullPayment(string storeId, public IActionResult ArchivePullPayment(string storeId,
string pullPaymentId) string pullPaymentId)
{ {
@ -265,11 +265,11 @@ namespace BTCPayServer.Controllers
} }
[HttpPost("stores/{storeId}/pull-payments/{pullPaymentId}/archive")] [HttpPost("stores/{storeId}/pull-payments/{pullPaymentId}/archive")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanArchivePullPayments, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ArchivePullPaymentPost(string storeId, public async Task<IActionResult> ArchivePullPaymentPost(string storeId,
string pullPaymentId) string pullPaymentId)
{ {
await _pullPaymentService.Cancel(new HostedServices.PullPaymentHostedService.CancelRequest(pullPaymentId)); await _pullPaymentService.Cancel(new PullPaymentHostedService.CancelRequest(pullPaymentId));
TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel()
{ {
Message = "Pull payment archived", Message = "Pull payment archived",

View file

@ -25,7 +25,7 @@
<vc:icon symbol="copy" /> <vc:icon symbol="copy" />
</button> </button>
</div> </div>
<p v-if="note" v-html="note" class="text-muted mt-3"></p> <div v-if="note" v-html="note" class="text-muted mt-3" id="scan-qr-modal-note"></div>
</div> </div>
<div class="mb-4 text-center" v-if="continueCallback"> <div class="mb-4 text-center" v-if="continueCallback">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" v-on:click="continueCallback()">{{ continueTitle || 'Continue' }}</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" v-on:click="continueCallback()">{{ continueTitle || 'Continue' }}</button>
@ -34,6 +34,9 @@
</div> </div>
</div> </div>
</div> </div>
<style>
#scan-qr-modal-note :last-child { margin-bottom: 0; }
</style>
<script> <script>
function initQRShow(data) { function initQRShow(data) {
return new Vue({ return new Vue({
@ -106,7 +109,10 @@ function initQRShow(data) {
}, },
showData(data) { showData(data) {
this.modes = { default: { title: 'Default', fragments: [data] } }; this.modes = { default: { title: 'Default', fragments: [data] } };
this.mode = "default";
this.show();
},
show(){
$(`#${this.modalId}`).modal("show"); $(`#${this.modalId}`).modal("show");
} }
} }

View file

@ -46,8 +46,8 @@
<div class="input-group"> <div class="input-group">
@if (Model.LnurlEndpoint is not null) @if (Model.LnurlEndpoint is not null)
{ {
<button type="button" class="input-group-prepend btn btn-outline-secondary" id="lnurlwithdraw-button" data-bs-toggle="modal" data-bs-target="#scan-qr-modal"> <button type="button" class="btn btn-secondary" id="lnurlwithdraw-button">
<span class="fa fa-qrcode fa-2x" title="LNURL-Withdraw"></span> <span class="fa fa-qrcode fa-2x" title="LNURL-Withdraw"></span>
</button> </button>
} }
<input class="form-control form-control-lg font-monospace" asp-for="Destination" placeholder="Enter destination to claim funds" required style="font-size:.9rem;height:42px;"> <input class="form-control form-control-lg font-monospace" asp-for="Destination" placeholder="Enter destination to claim funds" required style="font-size:.9rem;height:42px;">
@ -77,7 +77,6 @@
</div> </div>
</nav> </nav>
} }
<main class="flex-grow-1 py-4"> <main class="flex-grow-1 py-4">
<div class="container"> <div class="container">
<partial name="_StatusMessage" model="@(new ViewDataDictionary(ViewData){ { "Margin", "mb-4" } })" /> <partial name="_StatusMessage" model="@(new ViewDataDictionary(ViewData){ { "Margin", "mb-4" } })" />
@ -105,6 +104,10 @@
<button type="button" class="btn btn-link fw-semibold d-none d-lg-inline-block d-print-none border-0 p-0 ms-4 only-for-js" id="copyLink"> <button type="button" class="btn btn-link fw-semibold d-none d-lg-inline-block d-print-none border-0 p-0 ms-4 only-for-js" id="copyLink">
Copy Link Copy Link
</button> </button>
<button type="button" class="btn btn-link fw-semibold d-inline-block d-print-none border-0 p-0 ms-4 only-for-js" page-qr>
<span class="fa fa-qrcode"></span> Show QR
</button>
</div> </div>
@if (!string.IsNullOrEmpty(Model.ResetIn)) @if (!string.IsNullOrEmpty(Model.ResetIn))
{ {
@ -205,33 +208,49 @@
</footer> </footer>
</div> </div>
<partial name="LayoutFoot" /> <partial name="LayoutFoot" />
@if (Model.LnurlEndpoint is not null) <script src="~/vendor/vue-qrcode/vue-qrcode.min.js" asp-append-version="true"></script>
{ <script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></script>
var lnurlUri = LNURL.LNURL.EncodeUri(Model.LnurlEndpoint, "withdrawRequest", false).ToString(); <partial name="ShowQR" />
var lnurlBech32 = LNURL.LNURL.EncodeUri(Model.LnurlEndpoint, "withdrawRequest", true).ToString(); <script>
var note = "You can scan or open this link with a <a href='https://github.com/fiatjaf/lnurl-rfc#lnurl-documents' target='_blank' rel='noreferrer noopener'>LNURL-Withdraw</a> enabled wallet.";
if (!Model.AutoApprove)
{
note += "<br/><span class='fw-bold'>Please note that this pull payment does not automatically send out funds, and so will process payment after LNURL-withdraw flow is completed.</span>";
}
<script src="~/vendor/vue-qrcode/vue-qrcode.min.js" asp-append-version="true"></script>
<script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></script>
<partial name="ShowQR"/>
<script>
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const modes = { window.qrApp = initQRShow({});
uri: { title: "URI", fragments: [@Safe.Json(lnurlUri)], showData: true, href: @Safe.Json(lnurlUri) }, delegate('click', 'button[page-qr]', event => {
bech32: { title: "Bech32", fragments: [@Safe.Json(lnurlBech32)], showData: true, href: @Safe.Json(lnurlBech32) } qrApp.title = "Pull Payment QR";
}; qrApp.note = "Scan this QR code to open this page on your mobile device.";
initQRShow({ title: "LNURL Withdraw", note: @Safe.Json(note), modes }) qrApp.showData(window.location.href);
}); });
});
</script> </script>
} @if (Model.LnurlEndpoint is not null)
<script> {
var lnurlUri = LNURL.LNURL.EncodeUri(Model.LnurlEndpoint, "withdrawRequest", false).ToString();
var lnurlBech32 = LNURL.LNURL.EncodeUri(Model.LnurlEndpoint, "withdrawRequest", true).ToString();
var note = "<p>You can scan or open this link with a <a href='https://github.com/fiatjaf/lnurl-rfc#lnurl-documents' target='_blank' rel='noreferrer noopener'>LNURL-Withdraw</a> enabled wallet.</p>";
if (!Model.AutoApprove)
{
note += "<p class='fw-semibold'>Please note that this pull payment does not automatically send out funds, but will process the payment after the LNURL-withdraw flow is completed.</p>";
}
<script>
document.addEventListener("DOMContentLoaded", () => {
const modes = {
uri: { title: "URI", fragments: [@Safe.Json(lnurlUri)], showData: true, href: @Safe.Json(lnurlUri) },
bech32: { title: "Bech32", fragments: [@Safe.Json(lnurlBech32)], showData: true, href: @Safe.Json(lnurlBech32) }
};
delegate('click', '#lnurlwithdraw-button', () => {
qrApp.title = "LNURL Withdraw";
qrApp.modes = modes;
qrApp.mode = "bech32";
qrApp.note = @Safe.Json(note);
qrApp.show();
});
});
</script>
}
<script>
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
document.getElementById("copyLink").addEventListener("click", window.copyUrlToClipboard); document.getElementById("copyLink").addEventListener("click", window.copyUrlToClipboard);
}); });
</script> </script>
<vc:ui-extension-point location="pullpayment-foot" model="@Model"></vc:ui-extension-point> <vc:ui-extension-point location="pullpayment-foot" model="@Model"></vc:ui-extension-point>
</body> </body>
</html> </html>

View file

@ -71,7 +71,7 @@
{ {
<a id="@state-view" <a id="@state-view"
asp-action="PullPayments" asp-action="PullPayments"
asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-storeId="@storeId"
asp-route-pullPaymentState="@state" asp-route-pullPaymentState="@state"
class="nav-link @(state == Model.ActiveState ? "active" : "")" role="tab">@state</a> class="nav-link @(state == Model.ActiveState ? "active" : "")" role="tab">@state</a>
} }
@ -100,8 +100,9 @@
} }
</script> </script>
} }
<table class="table table-hover table-responsive-lg"> <div class="table-responsive">
<thead class="thead-inverse"> <table class="table table-hover">
<thead class="thead-inverse">
<tr> <tr>
<th scope="col"> <th scope="col">
<a asp-action="PullPayments" <a asp-action="PullPayments"
@ -118,63 +119,63 @@
<th scope="col">Refunded</th> <th scope="col">Refunded</th>
<th scope="col" class="text-end">Actions</th> <th scope="col" class="text-end">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach (var pp in Model.PullPayments) @foreach (var pp in Model.PullPayments)
{ {
<tr> <tr>
<td>@pp.StartDate.ToBrowserDate()</td> <td>@pp.StartDate.ToBrowserDate()</td>
<td> <td>
<a asp-action="EditPullPayment" <a asp-action="EditPullPayment"
asp-controller="UIPullPayment" asp-controller="UIPullPayment"
asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-storeId="@storeId"
asp-route-pullPaymentId="@pp.Id"> asp-route-pullPaymentId="@pp.Id">
@pp.Name @pp.Name
</a>
</td>
<td>@pp.AutoApproveClaims</td>
<td class="align-middle">
<div class="progress ppProgress" data-pp="@pp.Id" data-bs-toggle="tooltip" data-bs-html="true">
<div class="progress-bar" role="progressbar" aria-valuenow="@pp.Progress.CompletedPercent"
aria-valuemin="0" aria-valuemax="100" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width:@(pp.Progress.CompletedPercent)%;">
</div>
<div class="progress-bar" role="progressbar" aria-valuenow="@pp.Progress.AwaitingPercent"
aria-valuemin="0" aria-valuemax="100" style="background-color:orange; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width:@(pp.Progress.AwaitingPercent)%;">
</div>
</div>
</td>
<td class="text-end">
<a class="pp-payout"
asp-action="Payouts"
asp-route-storeId="@Context.GetRouteValue("storeId")"
asp-route-pullPaymentId="@pp.Id">
Payouts
</a>
@if (!pp.Archived)
{
<span permission="@Policies.CanModifyStoreSettings"> - </span>
<a asp-action="ArchivePullPayment"
permission="@Policies.CanModifyStoreSettings"
asp-route-storeId="@Context.GetRouteValue("storeId")"
asp-route-pullPaymentId="@pp.Id"
data-bs-toggle="modal"
data-bs-target="#ConfirmModal"
data-description="Do you really want to archive the pull payment <strong>@Html.Encode(pp.Name)</strong>?">
Archive
</a> </a>
} </td>
<span> - </span> <td>@pp.AutoApproveClaims</td>
<a asp-action="ViewPullPayment" <td class="align-middle">
asp-controller="UIPullPayment" <div class="progress ppProgress" data-pp="@pp.Id" data-bs-toggle="tooltip" data-bs-html="true">
asp-route-pullPaymentId="@pp.Id"> <div class="progress-bar" role="progressbar" aria-valuenow="@pp.Progress.CompletedPercent"
View aria-valuemin="0" aria-valuemax="100" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width:@(pp.Progress.CompletedPercent)%;">
</a> </div>
</td> <div class="progress-bar" role="progressbar" aria-valuenow="@pp.Progress.AwaitingPercent"
</tr> aria-valuemin="0" aria-valuemax="100" style="background-color:orange; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width:@(pp.Progress.AwaitingPercent)%;">
} </div>
</tbody> </div>
</table> </td>
<td class="text-end">
<a class="pp-payout"
asp-action="Payouts"
asp-route-storeId="@storeId"
asp-route-pullPaymentId="@pp.Id">
Payouts
</a>
@if (!pp.Archived)
{
<span permission="@Policies.CanArchivePullPayments"> - </span>
<a asp-action="ArchivePullPayment"
permission="@Policies.CanArchivePullPayments"
asp-route-storeId="@storeId"
asp-route-pullPaymentId="@pp.Id"
data-bs-toggle="modal"
data-bs-target="#ConfirmModal"
data-description="Do you really want to archive the pull payment <strong>@Html.Encode(pp.Name)</strong>?">
Archive
</a>
}
<span> - </span>
<a asp-action="ViewPullPayment"
asp-controller="UIPullPayment"
asp-route-pullPaymentId="@pp.Id">
View
</a>
</td>
</tr>
}
</tbody>
</table>
</div>
<vc:pager view-model="Model" /> <vc:pager view-model="Model" />
<partial name="_Confirm" model="@(new ConfirmModel("Archive pull payment", "Do you really want to archive the pull payment?", "Archive"))" /> <partial name="_Confirm" model="@(new ConfirmModel("Archive pull payment", "Do you really want to archive the pull payment?", "Archive"))" />

View file

@ -120,7 +120,7 @@
"securitySchemes": { "securitySchemes": {
"API_Key": { "API_Key": {
"type": "apiKey", "type": "apiKey",
"description": "BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n* `unrestricted`: Unrestricted access\n* `btcpay.user.candeleteuser`: Delete user\n* `btcpay.user.canviewprofile`: View your profile\n* `btcpay.user.canmodifyprofile`: Manage your profile\n* `btcpay.user.canmanagenotificationsforuser`: Manage your notifications\n* `btcpay.user.canviewnotificationsforuser`: View your notifications\n\nThe following permissions are available if the user is an administrator:\n\n* `btcpay.server.canviewusers`: View users\n* `btcpay.server.cancreateuser`: Create new users\n* `btcpay.server.canmanageusers`: Manage users\n* `btcpay.server.canmodifyserversettings`: Manage your server\n* `btcpay.server.canuseinternallightningnode`: Use the internal lightning node\n* `btcpay.server.canviewlightninginvoiceinternalnode`: View invoices from internal lightning node\n* `btcpay.server.cancreatelightninginvoiceinternalnode`: Create invoices with internal lightning node\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n* `btcpay.store.canmodifystoresettings`: Modify your stores\n* `btcpay.store.canviewcustodianaccounts`: View exchange accounts linked to your stores\n* `btcpay.store.canmanagecustodianaccounts`: Manage exchange accounts linked to your stores\n* `btcpay.store.candeposittocustodianaccount`: Deposit funds to exchange accounts linked to your stores\n* `btcpay.store.canwithdrawfromcustodianaccount`: Withdraw funds from exchange accounts to your store\n* `btcpay.store.cantradecustodianaccount`: Trade funds on your store's exchange accounts\n* `btcpay.store.webhooks.canmodifywebhooks`: Modify stores webhooks\n* `btcpay.store.canviewstoresettings`: View your stores\n* `btcpay.store.cancreateinvoice`: Create an invoice\n* `btcpay.store.canviewinvoices`: View invoices\n* `btcpay.store.canmodifyinvoices`: Modify invoices\n* `btcpay.store.canmodifypaymentrequests`: Modify your payment requests\n* `btcpay.store.canviewpaymentrequests`: View your payment requests\n* `btcpay.store.canmanagepullpayments`: Manage your pull payments\n* `btcpay.store.cancreatepullpayments`: Create pull payments\n* `btcpay.store.cancreatenonapprovedpullpayments`: Create non-approved pull payments\n* `btcpay.store.canuselightningnode`: Use the lightning nodes associated with your stores\n* `btcpay.store.canviewlightninginvoice`: View the lightning invoices associated with your stores\n* `btcpay.store.cancreatelightninginvoice`: Create invoices from the lightning nodes associated with your stores\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\nSome permissions may include other permissions, see [this operation](#operation/permissionsMetadata).\n", "description": "BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n* `unrestricted`: Unrestricted access\n* `btcpay.user.candeleteuser`: Delete user\n* `btcpay.user.canviewprofile`: View your profile\n* `btcpay.user.canmodifyprofile`: Manage your profile\n* `btcpay.user.canmanagenotificationsforuser`: Manage your notifications\n* `btcpay.user.canviewnotificationsforuser`: View your notifications\n\nThe following permissions are available if the user is an administrator:\n\n* `btcpay.server.canviewusers`: View users\n* `btcpay.server.cancreateuser`: Create new users\n* `btcpay.server.canmanageusers`: Manage users\n* `btcpay.server.canmodifyserversettings`: Manage your server\n* `btcpay.server.canuseinternallightningnode`: Use the internal lightning node\n* `btcpay.server.canviewlightninginvoiceinternalnode`: View invoices from internal lightning node\n* `btcpay.server.cancreatelightninginvoiceinternalnode`: Create invoices with internal lightning node\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n* `btcpay.store.canmodifystoresettings`: Modify your stores\n* `btcpay.store.canviewcustodianaccounts`: View exchange accounts linked to your stores\n* `btcpay.store.canmanagecustodianaccounts`: Manage exchange accounts linked to your stores\n* `btcpay.store.candeposittocustodianaccount`: Deposit funds to exchange accounts linked to your stores\n* `btcpay.store.canwithdrawfromcustodianaccount`: Withdraw funds from exchange accounts to your store\n* `btcpay.store.cantradecustodianaccount`: Trade funds on your store's exchange accounts\n* `btcpay.store.webhooks.canmodifywebhooks`: Modify stores webhooks\n* `btcpay.store.canviewstoresettings`: View your stores\n* `btcpay.store.cancreateinvoice`: Create an invoice\n* `btcpay.store.canviewinvoices`: View invoices\n* `btcpay.store.canmodifyinvoices`: Modify invoices\n* `btcpay.store.canmodifypaymentrequests`: Modify your payment requests\n* `btcpay.store.canviewpaymentrequests`: View your payment requests\n* `btcpay.store.canmanagepullpayments`: Manage your pull payments\n* `btcpay.store.canarchivepullpayments`: Archive your pull payments\n* `btcpay.store.cancreatepullpayments`: Create pull payments\n* `btcpay.store.cancreatenonapprovedpullpayments`: Create non-approved pull payments\n* `btcpay.store.canuselightningnode`: Use the lightning nodes associated with your stores\n* `btcpay.store.canviewlightninginvoice`: View the lightning invoices associated with your stores\n* `btcpay.store.cancreatelightninginvoice`: Create invoices from the lightning nodes associated with your stores\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\nSome permissions may include other permissions, see [this operation](#operation/permissionsMetadata).\n",
"name": "Authorization", "name": "Authorization",
"in": "header" "in": "header"
}, },

View file

@ -243,7 +243,7 @@
"security": [ "security": [
{ {
"API_Key": [ "API_Key": [
"btcpay.store.canmanagepullpayments" "btcpay.store.canarchivepullpayments"
], ],
"Basic": [] "Basic": []
} }