mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Fix payment request cloning and unexpire if necessary (#2820)
* Unexpire payment requests without expiry date * Unset expiry date when cloning payment request * Syntax and code improvements
This commit is contained in:
@@ -571,6 +571,22 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.Name("ViewAppButton")).Click();
|
s.Driver.FindElement(By.Name("ViewAppButton")).Click();
|
||||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text);
|
Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text);
|
||||||
|
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
||||||
|
|
||||||
|
// expire
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
|
s.Driver.ExecuteJavaScript("document.getElementById('ExpiryDate').value = '2021-01-21T21:00:00.000Z'");
|
||||||
|
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
|
Assert.Equal("Expired", s.Driver.FindElement(By.CssSelector("[data-test='status']")).Text);
|
||||||
|
|
||||||
|
// unexpire
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
|
s.Driver.FindElement(By.Id("ClearExpiryDate")).Click();
|
||||||
|
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
|
s.Driver.AssertElementNotFound(By.CssSelector("[data-test='status']"));
|
||||||
|
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,8 +62,7 @@ namespace BTCPayServer.Controllers
|
|||||||
_linkGenerator = linkGenerator;
|
_linkGenerator = linkGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet("")]
|
||||||
[Route("")]
|
|
||||||
[BitpayAPIConstraint(false)]
|
[BitpayAPIConstraint(false)]
|
||||||
public async Task<IActionResult> GetPaymentRequests(ListPaymentRequestsViewModel model = null)
|
public async Task<IActionResult> GetPaymentRequests(ListPaymentRequestsViewModel model = null)
|
||||||
{
|
{
|
||||||
@@ -83,22 +82,20 @@ namespace BTCPayServer.Controllers
|
|||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet("edit/{id?}")]
|
||||||
[Route("edit/{id?}")]
|
|
||||||
public async Task<IActionResult> EditPaymentRequest(string id)
|
public async Task<IActionResult> EditPaymentRequest(string id)
|
||||||
{
|
{
|
||||||
SelectList stores = null;
|
|
||||||
var data = await _PaymentRequestRepository.FindPaymentRequest(id, GetUserId());
|
var data = await _PaymentRequestRepository.FindPaymentRequest(id, GetUserId());
|
||||||
if (data == null && !string.IsNullOrEmpty(id))
|
if (data == null && !string.IsNullOrEmpty(id))
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
stores = new SelectList(await _StoreRepository.GetStoresByUserId(GetUserId()), nameof(StoreData.Id),
|
SelectList stores = new SelectList(await _StoreRepository.GetStoresByUserId(GetUserId()), nameof(StoreData.Id),
|
||||||
nameof(StoreData.StoreName), data?.StoreDataId);
|
nameof(StoreData.StoreName), data?.StoreDataId);
|
||||||
if (!stores.Any())
|
if (!stores.Any())
|
||||||
{
|
{
|
||||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||||
{
|
{
|
||||||
Html =
|
Html =
|
||||||
$"Error: You need to create at least one store. <a href='{Url.Action("CreateStore", "UserStores")}' class='alert-link'>Create store</a>",
|
$"Error: You need to create at least one store. <a href='{Url.Action("CreateStore", "UserStores")}' class='alert-link'>Create store</a>",
|
||||||
@@ -110,8 +107,7 @@ namespace BTCPayServer.Controllers
|
|||||||
return View(nameof(EditPaymentRequest), new UpdatePaymentRequestViewModel(data) { Stores = stores });
|
return View(nameof(EditPaymentRequest), new UpdatePaymentRequestViewModel(data) { Stores = stores });
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost("edit/{id?}")]
|
||||||
[Route("edit/{id?}")]
|
|
||||||
public async Task<IActionResult> EditPaymentRequest(string id, UpdatePaymentRequestViewModel viewModel)
|
public async Task<IActionResult> EditPaymentRequest(string id, UpdatePaymentRequestViewModel viewModel)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(viewModel.Currency) ||
|
if (string.IsNullOrEmpty(viewModel.Currency) ||
|
||||||
@@ -124,7 +120,7 @@ namespace BTCPayServer.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data?.Archived is true && viewModel?.Archived is true)
|
if (data?.Archived is true && viewModel.Archived is true)
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(string.Empty, "You cannot edit an archived payment request.");
|
ModelState.AddModelError(string.Empty, "You cannot edit an archived payment request.");
|
||||||
}
|
}
|
||||||
@@ -164,14 +160,13 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
data = await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(data);
|
data = await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(data);
|
||||||
_EventAggregator.Publish(new PaymentRequestUpdated() { Data = data, PaymentRequestId = data.Id, });
|
_EventAggregator.Publish(new PaymentRequestUpdated { Data = data, PaymentRequestId = data.Id, });
|
||||||
|
|
||||||
TempData[WellKnownTempData.SuccessMessage] = "Saved";
|
TempData[WellKnownTempData.SuccessMessage] = "Saved";
|
||||||
return RedirectToAction(nameof(EditPaymentRequest), new { id = data.Id });
|
return RedirectToAction(nameof(EditPaymentRequest), new { id = data.Id });
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet("{id}")]
|
||||||
[Route("{id}")]
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<IActionResult> ViewPaymentRequest(string id)
|
public async Task<IActionResult> ViewPaymentRequest(string id)
|
||||||
{
|
{
|
||||||
@@ -181,12 +176,11 @@ namespace BTCPayServer.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
result.HubPath = PaymentRequestHub.GetHubPath(this.Request);
|
result.HubPath = PaymentRequestHub.GetHubPath(Request);
|
||||||
return View(result);
|
return View(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet("{id}/pay")]
|
||||||
[Route("{id}/pay")]
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<IActionResult> PayPaymentRequest(string id, bool redirectToInvoice = true,
|
public async Task<IActionResult> PayPaymentRequest(string id, bool redirectToInvoice = true,
|
||||||
decimal? amount = null, CancellationToken cancellationToken = default)
|
decimal? amount = null, CancellationToken cancellationToken = default)
|
||||||
@@ -212,7 +206,7 @@ namespace BTCPayServer.Controllers
|
|||||||
return BadRequest("Payment Request cannot be paid as it has been archived");
|
return BadRequest("Payment Request cannot be paid as it has been archived");
|
||||||
}
|
}
|
||||||
|
|
||||||
result.HubPath = PaymentRequestHub.GetHubPath(this.Request);
|
result.HubPath = PaymentRequestHub.GetHubPath(Request);
|
||||||
if (result.AmountDue <= 0)
|
if (result.AmountDue <= 0)
|
||||||
{
|
{
|
||||||
if (redirectToInvoice)
|
if (redirectToInvoice)
|
||||||
@@ -233,7 +227,7 @@ namespace BTCPayServer.Controllers
|
|||||||
return BadRequest("Payment Request has expired");
|
return BadRequest("Payment Request has expired");
|
||||||
}
|
}
|
||||||
|
|
||||||
var stateAllowedToDisplay = new HashSet<InvoiceState>()
|
var stateAllowedToDisplay = new HashSet<InvoiceState>
|
||||||
{
|
{
|
||||||
new InvoiceState(InvoiceStatusLegacy.New, InvoiceExceptionStatus.None),
|
new InvoiceState(InvoiceStatusLegacy.New, InvoiceExceptionStatus.None),
|
||||||
new InvoiceState(InvoiceStatusLegacy.New, InvoiceExceptionStatus.PaidPartial),
|
new InvoiceState(InvoiceStatusLegacy.New, InvoiceExceptionStatus.PaidPartial),
|
||||||
@@ -245,7 +239,7 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
if (redirectToInvoice)
|
if (redirectToInvoice)
|
||||||
{
|
{
|
||||||
return RedirectToAction("Checkout", "Invoice", new { Id = currentInvoice.Id });
|
return RedirectToAction("Checkout", "Invoice", new { currentInvoice.Id });
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(currentInvoice.Id);
|
return Ok(currentInvoice.Id);
|
||||||
@@ -255,8 +249,7 @@ namespace BTCPayServer.Controllers
|
|||||||
amount = Math.Min(result.AmountDue, amount.Value);
|
amount = Math.Min(result.AmountDue, amount.Value);
|
||||||
else
|
else
|
||||||
amount = result.AmountDue;
|
amount = result.AmountDue;
|
||||||
|
|
||||||
|
|
||||||
var pr = await _PaymentRequestRepository.FindPaymentRequest(id, null, cancellationToken);
|
var pr = await _PaymentRequestRepository.FindPaymentRequest(id, null, cancellationToken);
|
||||||
var blob = pr.GetBlob();
|
var blob = pr.GetBlob();
|
||||||
var store = pr.StoreData;
|
var store = pr.StoreData;
|
||||||
@@ -281,12 +274,12 @@ namespace BTCPayServer.Controllers
|
|||||||
Checkout = {RedirectURL = redirectUrl}
|
Checkout = {RedirectURL = redirectUrl}
|
||||||
};
|
};
|
||||||
|
|
||||||
var additionalTags = new List<string>() {PaymentRequestRepository.GetInternalTag(id)};
|
var additionalTags = new List<string> {PaymentRequestRepository.GetInternalTag(id)};
|
||||||
var newInvoice = await _InvoiceController.CreateInvoiceCoreRaw(invoiceRequest,store, "/",additionalTags, cancellationToken);
|
var newInvoice = await _InvoiceController.CreateInvoiceCoreRaw(invoiceRequest,store, "/",additionalTags, cancellationToken);
|
||||||
|
|
||||||
if (redirectToInvoice)
|
if (redirectToInvoice)
|
||||||
{
|
{
|
||||||
return RedirectToAction("Checkout", "Invoice", new { Id = newInvoice.Id });
|
return RedirectToAction("Checkout", "Invoice", new { newInvoice.Id });
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(newInvoice.Id);
|
return Ok(newInvoice.Id);
|
||||||
@@ -297,8 +290,7 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet("{id}/cancel")]
|
||||||
[Route("{id}/cancel")]
|
|
||||||
public async Task<IActionResult> CancelUnpaidPendingInvoice(string id, bool redirect = true)
|
public async Task<IActionResult> CancelUnpaidPendingInvoice(string id, bool redirect = true)
|
||||||
{
|
{
|
||||||
var result = await _PaymentRequestService.GetPaymentRequest(id, GetUserId());
|
var result = await _PaymentRequestService.GetPaymentRequest(id, GetUserId());
|
||||||
@@ -334,8 +326,7 @@ namespace BTCPayServer.Controllers
|
|||||||
return _UserManager.GetUserId(User);
|
return _UserManager.GetUserId(User);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet("{id}/clone")]
|
||||||
[Route("{id}/clone")]
|
|
||||||
public async Task<IActionResult> ClonePaymentRequest(string id)
|
public async Task<IActionResult> ClonePaymentRequest(string id)
|
||||||
{
|
{
|
||||||
var result = await EditPaymentRequest(id);
|
var result = await EditPaymentRequest(id);
|
||||||
@@ -344,6 +335,7 @@ namespace BTCPayServer.Controllers
|
|||||||
var model = (UpdatePaymentRequestViewModel)viewResult.Model;
|
var model = (UpdatePaymentRequestViewModel)viewResult.Model;
|
||||||
model.Id = null;
|
model.Id = null;
|
||||||
model.Archived = false;
|
model.Archived = false;
|
||||||
|
model.ExpiryDate = null;
|
||||||
model.Title = $"Clone of {model.Title}";
|
model.Title = $"Clone of {model.Title}";
|
||||||
return View("EditPaymentRequest", model);
|
return View("EditPaymentRequest", model);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ namespace BTCPayServer.PaymentRequest
|
|||||||
private readonly CurrencyNameTable _currencies;
|
private readonly CurrencyNameTable _currencies;
|
||||||
|
|
||||||
public PaymentRequestService(
|
public PaymentRequestService(
|
||||||
IHubContext<PaymentRequestHub> hubContext,
|
|
||||||
PaymentRequestRepository paymentRequestRepository,
|
PaymentRequestRepository paymentRequestRepository,
|
||||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||||
AppService appService,
|
AppService appService,
|
||||||
@@ -49,17 +48,19 @@ namespace BTCPayServer.PaymentRequest
|
|||||||
if (blob.ExpiryDate.Value <= DateTimeOffset.UtcNow)
|
if (blob.ExpiryDate.Value <= DateTimeOffset.UtcNow)
|
||||||
currentStatus = Client.Models.PaymentRequestData.PaymentRequestStatus.Expired;
|
currentStatus = Client.Models.PaymentRequestData.PaymentRequestStatus.Expired;
|
||||||
}
|
}
|
||||||
|
else if (currentStatus != Client.Models.PaymentRequestData.PaymentRequestStatus.Completed)
|
||||||
|
{
|
||||||
|
currentStatus = Client.Models.PaymentRequestData.PaymentRequestStatus.Pending;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentStatus != Client.Models.PaymentRequestData.PaymentRequestStatus.Expired)
|
if (currentStatus != Client.Models.PaymentRequestData.PaymentRequestStatus.Expired)
|
||||||
{
|
{
|
||||||
var rateRules = pr.StoreData.GetStoreBlob().GetRateRules(_BtcPayNetworkProvider);
|
|
||||||
var invoices = await _PaymentRequestRepository.GetInvoicesForPaymentRequest(pr.Id);
|
var invoices = await _PaymentRequestRepository.GetInvoicesForPaymentRequest(pr.Id);
|
||||||
var contributions = _AppService.GetContributionsByPaymentMethodId(blob.Currency, invoices, true);
|
var contributions = _AppService.GetContributionsByPaymentMethodId(blob.Currency, invoices, true);
|
||||||
|
|
||||||
currentStatus = contributions.TotalCurrency >= blob.Amount
|
currentStatus = contributions.TotalCurrency >= blob.Amount
|
||||||
? Client.Models.PaymentRequestData.PaymentRequestStatus.Completed
|
? Client.Models.PaymentRequestData.PaymentRequestStatus.Completed
|
||||||
: Client.Models.PaymentRequestData.PaymentRequestStatus.Pending;
|
: Client.Models.PaymentRequestData.PaymentRequestStatus.Pending;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStatus != pr.Status)
|
if (currentStatus != pr.Status)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
<input asp-for="ExpiryDate"
|
<input asp-for="ExpiryDate"
|
||||||
value="@(Model.ExpiryDate?.ToString("u", CultureInfo.InvariantCulture))"
|
value="@(Model.ExpiryDate?.ToString("u", CultureInfo.InvariantCulture))"
|
||||||
class="form-control flatdtpicker" min="today" placeholder="No expiry date has been set for this payment request" />
|
class="form-control flatdtpicker" min="today" placeholder="No expiry date has been set for this payment request" />
|
||||||
<button class="btn btn-secondary input-group-clear" type="button" title="Clear">
|
<button id="ClearExpiryDate" class="btn btn-secondary input-group-clear" type="button" title="Clear">
|
||||||
<span class="fa fa-times"></span>
|
<span class="fa fa-times"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -116,14 +116,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col mt-2 col-12 col-sm-6 mt-sm-0 col-md-12 mt-md-2">
|
<div class="col mt-2 col-12 col-sm-6 mt-sm-0 col-md-12 mt-md-2">
|
||||||
<button class="btn btn-primary w-100 text-nowrap" type="submit">Pay Invoice</button>
|
<button class="btn btn-primary w-100 text-nowrap" type="submit" data-test="pay-button">Pay Invoice</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<a class="btn btn-primary d-inline-block d-print-none w-100 text-nowrap @if (!(Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments)) { @("btn-lg") }" asp-action="PayPaymentRequest" asp-route-id="@Model.Id">
|
<a class="btn btn-primary d-inline-block d-print-none w-100 text-nowrap @if (!(Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments)) { @("btn-lg") }" asp-action="PayPaymentRequest" asp-route-id="@Model.Id" data-test="pay-button">
|
||||||
Pay Invoice
|
Pay Invoice
|
||||||
</a>
|
</a>
|
||||||
if (Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments)
|
if (Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments)
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="h2 text-md-end">
|
<div class="h2 text-md-end">
|
||||||
<span class="badge @if (Model.Status == "Settled") { @("bg-primary") } else if (Model.Status == "Expired") { @("bg-danger") } else { @("bg-info") }">
|
<span class="badge @if (Model.Status == "Settled") { @("bg-primary") } else if (Model.Status == "Expired") { @("bg-danger") } else { @("bg-info") }" data-test="status">
|
||||||
@Model.Status
|
@Model.Status
|
||||||
@if (Model.Archived)
|
@if (Model.Archived)
|
||||||
{
|
{
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col mt-2 col-12 col-sm-6 mt-sm-0 col-md-12 mt-md-2">
|
<div class="col mt-2 col-12 col-sm-6 mt-sm-0 col-md-12 mt-md-2">
|
||||||
<button class="btn btn-primary w-100 d-flex d-print-none align-items-center justify-content-center text-nowrap" v-bind:class="{ 'btn-disabled': loading}" :disabled="loading" type="submit">
|
<button class="btn btn-primary w-100 d-flex d-print-none align-items-center justify-content-center text-nowrap" v-bind:class="{ 'btn-disabled': loading}" :disabled="loading" type="submit" data-test="pay-button">
|
||||||
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<button class="btn btn-primary w-100 d-flex d-print-none align-items-center justify-content-center text-nowrap" :class="{ 'btn-lg': !(srvModel.anyPendingInvoice && !srvModel.pendingInvoiceHasPayments)}" v-on:click="pay(null)" :disabled="loading">
|
<button class="btn btn-primary w-100 d-flex d-print-none align-items-center justify-content-center text-nowrap" :class="{ 'btn-lg': !(srvModel.anyPendingInvoice && !srvModel.pendingInvoiceHasPayments)}" v-on:click="pay(null)" :disabled="loading" data-test="pay-button">
|
||||||
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -185,7 +185,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="h2 text-md-end">
|
<div class="h2 text-md-end">
|
||||||
<span class="badge" :class="{ 'bg-primary': srvModel.status === 'Settled', 'bg-danger': srvModel.status === 'Expired', 'bg-info': (srvModel.status !== 'Settled' && srvModel.status !== 'Expired') }">
|
<span class="badge" :class="{ 'bg-primary': srvModel.status === 'Settled', 'bg-danger': srvModel.status === 'Expired', 'bg-info': (srvModel.status !== 'Settled' && srvModel.status !== 'Expired') }" data-test="status">
|
||||||
{{srvModel.status}}
|
{{srvModel.status}}
|
||||||
<span v-if="srvModel.archived">(archived)</span>
|
<span v-if="srvModel.archived">(archived)</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user