diff --git a/BTCPayServer/Controllers/UIPaymentRequestController.cs b/BTCPayServer/Controllers/UIPaymentRequestController.cs index 0fd57fea0..88253eeae 100644 --- a/BTCPayServer/Controllers/UIPaymentRequestController.cs +++ b/BTCPayServer/Controllers/UIPaymentRequestController.cs @@ -465,6 +465,48 @@ namespace BTCPayServer.Controllers return NotFound(); } + [HttpPost("{payReqId}/changestate/{newState}")] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyPaymentRequests)] + public async Task ChangePaymentRequestState(string payReqId, string newState) + { + if (string.IsNullOrWhiteSpace(payReqId) || string.IsNullOrWhiteSpace(newState)) + { + return BadRequest("Invalid parameters"); + } + + var paymentRequest = await _PaymentRequestRepository.FindPaymentRequest(payReqId, GetUserId()); + var model = new PaymentRequestStateChangeModel(); + if (paymentRequest == null) + { + model.NotFound = true; + return NotFound(model); + } + + if (newState == "completed") + { + await _PaymentRequestRepository.UpdatePaymentRequestStatus(payReqId, PaymentRequestStatus.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); + } + + public class PaymentRequestStateChangeModel + { + public bool NotFound { get; set; } + public string? StatusString { get; set; } + } + + private string GetUserId() => _UserManager.GetUserId(User); private StoreData GetCurrentStore() => HttpContext.GetStoreData(); diff --git a/BTCPayServer/Views/UIPaymentRequest/GetPaymentRequests.cshtml b/BTCPayServer/Views/UIPaymentRequest/GetPaymentRequests.cshtml index f729817fc..e7036a147 100644 --- a/BTCPayServer/Views/UIPaymentRequest/GetPaymentRequests.cshtml +++ b/BTCPayServer/Views/UIPaymentRequest/GetPaymentRequests.cshtml @@ -114,7 +114,21 @@ @item.ReferenceId - @item.Status + @if (item.IsPending) + { +
+ + +
+ } + else + { + @item.Status + } @item.AmountFormatted @@ -154,3 +168,7 @@ else There are no payment requests matching your criteria.

} + + \ No newline at end of file diff --git a/BTCPayServer/wwwroot/main/site.js b/BTCPayServer/wwwroot/main/site.js index e6b283260..41b9cded9 100644 --- a/BTCPayServer/wwwroot/main/site.js +++ b/BTCPayServer/wwwroot/main/site.js @@ -276,6 +276,23 @@ document.addEventListener("DOMContentLoaded", () => { alert("Invoice state update failed"); } }) + + // 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 = `
${statusString}
` + } else { + $badge.classList.remove('pe-none'); + alert("Payment request state update failed"); + } + }) // Time Format delegate('click', '.switch-time-format', switchTimeFormat);