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:
d11n
2021-08-31 08:07:54 +02:00
committed by GitHub
parent 101fc51787
commit 723817e3f8
5 changed files with 46 additions and 37 deletions

View File

@@ -571,6 +571,22 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Name("ViewAppButton")).Click();
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("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());
}
}

View File

@@ -62,8 +62,7 @@ namespace BTCPayServer.Controllers
_linkGenerator = linkGenerator;
}
[HttpGet]
[Route("")]
[HttpGet("")]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> GetPaymentRequests(ListPaymentRequestsViewModel model = null)
{
@@ -83,22 +82,20 @@ namespace BTCPayServer.Controllers
return View(model);
}
[HttpGet]
[Route("edit/{id?}")]
[HttpGet("edit/{id?}")]
public async Task<IActionResult> EditPaymentRequest(string id)
{
SelectList stores = null;
var data = await _PaymentRequestRepository.FindPaymentRequest(id, GetUserId());
if (data == null && !string.IsNullOrEmpty(id))
{
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);
if (!stores.Any())
{
TempData.SetStatusMessageModel(new StatusMessageModel()
TempData.SetStatusMessageModel(new StatusMessageModel
{
Html =
$"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 });
}
[HttpPost]
[Route("edit/{id?}")]
[HttpPost("edit/{id?}")]
public async Task<IActionResult> EditPaymentRequest(string id, UpdatePaymentRequestViewModel viewModel)
{
if (string.IsNullOrEmpty(viewModel.Currency) ||
@@ -124,7 +120,7 @@ namespace BTCPayServer.Controllers
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.");
}
@@ -164,14 +160,13 @@ namespace BTCPayServer.Controllers
}
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";
return RedirectToAction(nameof(EditPaymentRequest), new { id = data.Id });
}
[HttpGet]
[Route("{id}")]
[HttpGet("{id}")]
[AllowAnonymous]
public async Task<IActionResult> ViewPaymentRequest(string id)
{
@@ -181,12 +176,11 @@ namespace BTCPayServer.Controllers
return NotFound();
}
result.HubPath = PaymentRequestHub.GetHubPath(this.Request);
result.HubPath = PaymentRequestHub.GetHubPath(Request);
return View(result);
}
[HttpGet]
[Route("{id}/pay")]
[HttpGet("{id}/pay")]
[AllowAnonymous]
public async Task<IActionResult> PayPaymentRequest(string id, bool redirectToInvoice = true,
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");
}
result.HubPath = PaymentRequestHub.GetHubPath(this.Request);
result.HubPath = PaymentRequestHub.GetHubPath(Request);
if (result.AmountDue <= 0)
{
if (redirectToInvoice)
@@ -233,7 +227,7 @@ namespace BTCPayServer.Controllers
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.PaidPartial),
@@ -245,7 +239,7 @@ namespace BTCPayServer.Controllers
{
if (redirectToInvoice)
{
return RedirectToAction("Checkout", "Invoice", new { Id = currentInvoice.Id });
return RedirectToAction("Checkout", "Invoice", new { currentInvoice.Id });
}
return Ok(currentInvoice.Id);
@@ -256,7 +250,6 @@ namespace BTCPayServer.Controllers
else
amount = result.AmountDue;
var pr = await _PaymentRequestRepository.FindPaymentRequest(id, null, cancellationToken);
var blob = pr.GetBlob();
var store = pr.StoreData;
@@ -281,12 +274,12 @@ namespace BTCPayServer.Controllers
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);
if (redirectToInvoice)
{
return RedirectToAction("Checkout", "Invoice", new { Id = newInvoice.Id });
return RedirectToAction("Checkout", "Invoice", new { newInvoice.Id });
}
return Ok(newInvoice.Id);
@@ -297,8 +290,7 @@ namespace BTCPayServer.Controllers
}
}
[HttpGet]
[Route("{id}/cancel")]
[HttpGet("{id}/cancel")]
public async Task<IActionResult> CancelUnpaidPendingInvoice(string id, bool redirect = true)
{
var result = await _PaymentRequestService.GetPaymentRequest(id, GetUserId());
@@ -334,8 +326,7 @@ namespace BTCPayServer.Controllers
return _UserManager.GetUserId(User);
}
[HttpGet]
[Route("{id}/clone")]
[HttpGet("{id}/clone")]
public async Task<IActionResult> ClonePaymentRequest(string id)
{
var result = await EditPaymentRequest(id);
@@ -344,6 +335,7 @@ namespace BTCPayServer.Controllers
var model = (UpdatePaymentRequestViewModel)viewResult.Model;
model.Id = null;
model.Archived = false;
model.ExpiryDate = null;
model.Title = $"Clone of {model.Title}";
return View("EditPaymentRequest", model);
}

View File

@@ -22,7 +22,6 @@ namespace BTCPayServer.PaymentRequest
private readonly CurrencyNameTable _currencies;
public PaymentRequestService(
IHubContext<PaymentRequestHub> hubContext,
PaymentRequestRepository paymentRequestRepository,
BTCPayNetworkProvider btcPayNetworkProvider,
AppService appService,
@@ -49,17 +48,19 @@ namespace BTCPayServer.PaymentRequest
if (blob.ExpiryDate.Value <= DateTimeOffset.UtcNow)
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)
{
var rateRules = pr.StoreData.GetStoreBlob().GetRateRules(_BtcPayNetworkProvider);
var invoices = await _PaymentRequestRepository.GetInvoicesForPaymentRequest(pr.Id);
var contributions = _AppService.GetContributionsByPaymentMethodId(blob.Currency, invoices, true);
currentStatus = contributions.TotalCurrency >= blob.Amount
? Client.Models.PaymentRequestData.PaymentRequestStatus.Completed
: Client.Models.PaymentRequestData.PaymentRequestStatus.Pending;
}
if (currentStatus != pr.Status)

View File

@@ -74,7 +74,7 @@
<input asp-for="ExpiryDate"
value="@(Model.ExpiryDate?.ToString("u", CultureInfo.InvariantCulture))"
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>
</button>
</div>

View File

@@ -116,14 +116,14 @@
</div>
</div>
<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>
</form>
}
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
</a>
if (Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments)
@@ -137,7 +137,7 @@
else
{
<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
@if (Model.Archived)
{
@@ -158,7 +158,7 @@
</div>
</div>
<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">
<span class="visually-hidden">Loading...</span>
</div>
@@ -169,7 +169,7 @@
</form>
</template>
<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">
<span class="visually-hidden">Loading...</span>
</div>
@@ -185,7 +185,7 @@
</template>
<template v-else>
<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}}
<span v-if="srvModel.archived">(archived)</span>
</span>