Simplifying implementation of status switch

This commit is contained in:
rockstardev
2025-07-03 00:00:43 +02:00
parent bc1cebd2d1
commit 9c81666b38
3 changed files with 23 additions and 59 deletions

View File

@@ -465,48 +465,31 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
} }
[HttpPost("{payReqId}/changestate/{newState}")] [HttpPost("{payReqId}/complete")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyPaymentRequests)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyPaymentRequests)]
public async Task<IActionResult> ChangePaymentRequestState(string payReqId, string newState) public async Task<IActionResult> TogglePaymentRequestCompleted(string payReqId)
{ {
if (string.IsNullOrWhiteSpace(payReqId) || string.IsNullOrWhiteSpace(newState)) if (string.IsNullOrWhiteSpace(payReqId))
{ {
return BadRequest("Invalid parameters"); return BadRequest("Invalid parameters");
} }
var paymentRequest = await _PaymentRequestRepository.FindPaymentRequest(payReqId, GetUserId()); var paymentRequest = await _PaymentRequestRepository.FindPaymentRequest(payReqId, GetUserId());
var model = new PaymentRequestStateChangeModel();
if (paymentRequest == null) if (paymentRequest == null)
{ {
model.NotFound = true; return NotFound();
return NotFound(model);
} }
if (newState == "completed") if (paymentRequest.Status != PaymentRequestStatus.Pending)
{ {
await _PaymentRequestRepository.UpdatePaymentRequestStatus(payReqId, PaymentRequestStatus.Completed); return BadRequest("Invalid payment request status. Only pending payment requests can be marked as completed.");
model.StatusString = "Settled";
}
else if (newState == "expired")
{
await _PaymentRequestRepository.UpdatePaymentRequestStatus(payReqId, PaymentRequestStatus.Expired);
model.StatusString = "Expired";
}
else
{
return BadRequest($"Invalid state: {newState}");
} }
return Json(model); await _PaymentRequestRepository.UpdatePaymentRequestStatus(payReqId, PaymentRequestStatus.Completed);
return RedirectToAction("GetPaymentRequests", new { storeId = paymentRequest.StoreDataId });
} }
public class PaymentRequestStateChangeModel
{
public bool NotFound { get; set; }
public string? StatusString { get; set; }
}
private string GetUserId() => _UserManager.GetUserId(User); private string GetUserId() => _UserManager.GetUserId(User);
private StoreData GetCurrentStore() => HttpContext.GetStoreData(); private StoreData GetCurrentStore() => HttpContext.GetStoreData();

View File

@@ -18,7 +18,7 @@
{ {
private int CountArrayFilter(string type) => private int CountArrayFilter(string type) =>
Model.Search.ContainsFilter(type) ? Model.Search.GetFilterArray(type).Length : 0; Model.Search.ContainsFilter(type) ? Model.Search.GetFilterArray(type).Length : 0;
private bool HasArrayFilter(string type, string key = null) => private bool HasArrayFilter(string type, string key = null) =>
Model.Search.ContainsFilter(type) && (key is null || Model.Search.GetFilterArray(type).Contains(key)); Model.Search.ContainsFilter(type) && (key is null || Model.Search.GetFilterArray(type).Contains(key));
@@ -121,7 +121,9 @@
@item.Status @item.Status
</span> </span>
<div class="dropdown-menu"> <div class="dropdown-menu">
<button type="button" class="dropdown-item lh-base" data-payment-request-id="@item.Id" data-new-state="completed">Mark as settled</button> <form asp-action="TogglePaymentRequestCompleted" asp-route-payReqId="@item.Id" method="post">
<button type="submit" class="dropdown-item lh-base">Mark as settled</button>
</form>
</div> </div>
</div> </div>
} }
@@ -136,14 +138,14 @@
<td class="text-end"> <td class="text-end">
<div class="d-inline-flex align-items-center gap-3"> <div class="d-inline-flex align-items-center gap-3">
<a asp-action="ViewPaymentRequest" asp-route-payReqId="@item.Id" id="PaymentRequest-@item.Id" target="_blank" text-translate="true">View</a> <a asp-action="ViewPaymentRequest" asp-route-payReqId="@item.Id" id="PaymentRequest-@item.Id" target="_blank" text-translate="true">View</a>
<button type="button" class="btn btn-link p-0 clipboard-button" <button type="button" class="btn btn-link p-0 clipboard-button"
data-clipboard="@CallbackGenerator.PaymentRequestByIdLink(item.Id, this.Context.Request)" data-clipboard="@CallbackGenerator.PaymentRequestByIdLink(item.Id, this.Context.Request)"
title="Copy Link"> title="Copy Link">
<vc:icon symbol="actions-copy" /> <vc:icon symbol="actions-copy" />
</button> </button>
<div class="dropdown"> <div class="dropdown">
<button class="btn btn-link dropdown-toggle p-0 dropdown-toggle-no-caret text-body" type="button" data-bs-toggle="dropdown" aria-expanded="false" id="ToggleActions-@item.Id"> <button class="btn btn-link dropdown-toggle p-0 dropdown-toggle-no-caret text-body" type="button" data-bs-toggle="dropdown" aria-expanded="false" id="ToggleActions-@item.Id">
<vc:icon symbol="dots" /> <vc:icon symbol="dots" />
</button> </button>
<ul class="dropdown-menu" aria-labelledby="actionDropdown"> <ul class="dropdown-menu" aria-labelledby="actionDropdown">
<li><a class="dropdown-item" permission="@Policies.CanViewInvoices" asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@item.StoreId" asp-route-searchterm="@($"orderid:{PaymentRequestRepository.GetOrderIdForPaymentRequest(item.Id)}")" text-translate="true">Invoices</a></li> <li><a class="dropdown-item" permission="@Policies.CanViewInvoices" asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@item.StoreId" asp-route-searchterm="@($"orderid:{PaymentRequestRepository.GetOrderIdForPaymentRequest(item.Id)}")" text-translate="true">Invoices</a></li>
@@ -168,7 +170,3 @@ else
There are no payment requests matching your criteria. There are no payment requests matching your criteria.
</p> </p>
} }
<script>
window.storeId = '@storeId';
</script>

View File

@@ -151,7 +151,7 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
setStickyHeaderHeight(); setStickyHeaderHeight();
} }
// initialize timezone offset value if field is present in page // initialize timezone offset value if field is present in page
const $timezoneOffset = document.getElementById("TimezoneOffset"); const $timezoneOffset = document.getElementById("TimezoneOffset");
const timezoneOffset = new Date().getTimezoneOffset(); const timezoneOffset = new Date().getTimezoneOffset();
@@ -161,7 +161,7 @@ document.addEventListener("DOMContentLoaded", () => {
formatDateTimes(); formatDateTimes();
initLabelManagers(); initLabelManagers();
function updateTimeAgo(){ function updateTimeAgo(){
var timeagoElements = $("[data-timeago-unixms]"); var timeagoElements = $("[data-timeago-unixms]");
timeagoElements.each(function () { timeagoElements.each(function () {
@@ -171,7 +171,7 @@ document.addEventListener("DOMContentLoaded", () => {
setTimeout(updateTimeAgo, 1000); setTimeout(updateTimeAgo, 1000);
} }
updateTimeAgo(); updateTimeAgo();
// intializing date time pickers // intializing date time pickers
$(".flatdtpicker").each(function () { $(".flatdtpicker").each(function () {
var element = $(this); var element = $(this);
@@ -259,7 +259,7 @@ document.addEventListener("DOMContentLoaded", () => {
if (!!$button.innerHTML.match('#actions-hide')) $button.innerHTML = $button.innerHTML.replace('#actions-hide', '#actions-show'); if (!!$button.innerHTML.match('#actions-hide')) $button.innerHTML = $button.innerHTML.replace('#actions-hide', '#actions-show');
} }
}) })
// Invoice Status // Invoice Status
delegate('click', '[data-invoice-state-badge] [data-invoice-id][data-new-state]', async e => { delegate('click', '[data-invoice-state-badge] [data-invoice-id][data-new-state]', async e => {
const $button = e.target const $button = e.target
@@ -277,23 +277,6 @@ document.addEventListener("DOMContentLoaded", () => {
} }
}) })
// Payment Request Status
delegate('click', '[data-payment-request-state-badge] [data-payment-request-id][data-new-state]', async e => {
const $button = e.target
const $badge = $button.closest('[data-payment-request-state-badge]')
const { paymentRequestId, newState } = $button.dataset
$badge.classList.add('pe-none'); // disable further interaction
const response = await fetch(`${baseUrl}/payment-requests/${paymentRequestId}/changestate/${newState}`, { method: 'POST' })
if (response.ok) {
const { statusString } = await response.json()
$badge.outerHTML = `<div class="badge badge-${newState}" data-payment-request-state-badge="${paymentRequestId}">${statusString}</div>`
} else {
$badge.classList.remove('pe-none');
alert("Payment request state update failed");
}
})
// Time Format // Time Format
delegate('click', '.switch-time-format', switchTimeFormat); delegate('click', '.switch-time-format', switchTimeFormat);
@@ -319,7 +302,7 @@ document.addEventListener("DOMContentLoaded", () => {
document.documentElement.setAttribute(SENSITIVE_INFO_DATA_ATTR, 'true'); document.documentElement.setAttribute(SENSITIVE_INFO_DATA_ATTR, 'true');
} }
}); });
// Currency Selection: Remove the current input value once the element is focused, so that the user gets to // Currency Selection: Remove the current input value once the element is focused, so that the user gets to
// see the available options. If no selection or change is made, reset it to the previous value on blur. // see the available options. If no selection or change is made, reset it to the previous value on blur.
// Note: Use focusin/focusout instead of focus/blur, because the latter do not bubble up and delegate won't work. // Note: Use focusin/focusout instead of focus/blur, because the latter do not bubble up and delegate won't work.
@@ -331,7 +314,7 @@ document.addEventListener("DOMContentLoaded", () => {
if (!e.target.value) e.target.value = e.target.getAttribute('placeholder') if (!e.target.value) e.target.value = e.target.getAttribute('placeholder')
e.target.removeAttribute('placeholder') e.target.removeAttribute('placeholder')
}) })
// Offcanvas navigation // Offcanvas navigation
const mainMenuToggle = document.getElementById('mainMenuToggle') const mainMenuToggle = document.getElementById('mainMenuToggle')
if (mainMenuToggle) { if (mainMenuToggle) {
@@ -342,7 +325,7 @@ document.addEventListener("DOMContentLoaded", () => {
mainMenuToggle.setAttribute('aria-expanded', 'false') mainMenuToggle.setAttribute('aria-expanded', 'false')
}) })
} }
// Menu collapses // Menu collapses
const mainNav = document.getElementById('mainNav') const mainNav = document.getElementById('mainNav')
if (mainNav) { if (mainNav) {
@@ -361,7 +344,7 @@ document.addEventListener("DOMContentLoaded", () => {
window.localStorage.setItem(COLLAPSED_KEY, JSON.stringify(collapsed)) window.localStorage.setItem(COLLAPSED_KEY, JSON.stringify(collapsed))
}) })
} }
// Mass Action Tables // Mass Action Tables
const updateSelectedCount = ($table) => { const updateSelectedCount = ($table) => {
const selectedCount = document.querySelectorAll('.mass-action-select:checked').length; const selectedCount = document.querySelectorAll('.mass-action-select:checked').length;