Handle multiple new invoices in payment requests

This commit is contained in:
Kukks 2020-08-04 07:55:13 +02:00
parent 4dca905a91
commit b381e629f1
3 changed files with 62 additions and 26 deletions

View file

@ -3,9 +3,13 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Controllers; using BTCPayServer.Controllers;
using BTCPayServer.Models.PaymentRequestViewModels; using BTCPayServer.Models.PaymentRequestViewModels;
using BTCPayServer.PaymentRequest;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.PaymentRequests;
using BTCPayServer.Tests.Logging; using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using NBitcoin;
using NBitpayClient; using NBitpayClient;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -16,7 +20,7 @@ namespace BTCPayServer.Tests
{ {
public PaymentRequestTests(ITestOutputHelper helper) public PaymentRequestTests(ITestOutputHelper helper)
{ {
Logs.Tester = new XUnitLog(helper) { Name = "Tests" }; Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
Logs.LogProvider = new XUnitLogProvider(helper); Logs.LogProvider = new XUnitLogProvider(helper);
} }
@ -46,8 +50,8 @@ namespace BTCPayServer.Tests
Description = "description" Description = "description"
}; };
var id = (Assert var id = (Assert
.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(null, request)).RouteValues.Values.First().ToString()); .IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(null, request))
.RouteValues.Values.First().ToString());
//permission guard for guests editing //permission guard for guests editing
@ -57,7 +61,9 @@ namespace BTCPayServer.Tests
request.Title = "update"; request.Title = "update";
Assert.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(id, request)); Assert.IsType<RedirectToActionResult>(await paymentRequestController.EditPaymentRequest(id, request));
Assert.Equal(request.Title, Assert.IsType<ViewPaymentRequestViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Title); Assert.Equal(request.Title,
Assert.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Title);
Assert.False(string.IsNullOrEmpty(id)); Assert.False(string.IsNullOrEmpty(id));
@ -68,16 +74,24 @@ namespace BTCPayServer.Tests
Assert Assert
.IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id)); .IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id));
Assert.True(Assert.IsType<ViewPaymentRequestViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived); Assert.True(Assert
.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived);
Assert.Empty(Assert.IsType<ListPaymentRequestsViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests()).Model).Items); Assert.Empty(Assert
.IsType<ListPaymentRequestsViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests()).Model).Items);
//unarchive //unarchive
Assert Assert
.IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id)); .IsType<RedirectToActionResult>(await paymentRequestController.TogglePaymentRequestArchival(id));
Assert.False(Assert.IsType<ViewPaymentRequestViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived); Assert.False(Assert
.IsType<ViewPaymentRequestViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.ViewPaymentRequest(id)).Model).Archived);
Assert.Single(Assert.IsType<ListPaymentRequestsViewModel>(Assert.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests()).Model).Items); Assert.Single(Assert
.IsType<ListPaymentRequestsViewModel>(Assert
.IsType<ViewResult>(await paymentRequestController.GetPaymentRequests()).Model).Items);
} }
} }
@ -94,7 +108,8 @@ namespace BTCPayServer.Tests
var paymentRequestController = user.GetController<PaymentRequestController>(); var paymentRequestController = user.GetController<PaymentRequestController>();
Assert.IsType<NotFoundResult>(await paymentRequestController.PayPaymentRequest(Guid.NewGuid().ToString())); Assert.IsType<NotFoundResult>(
await paymentRequestController.PayPaymentRequest(Guid.NewGuid().ToString()));
var request = new UpdatePaymentRequestViewModel() var request = new UpdatePaymentRequestViewModel()
@ -110,15 +125,18 @@ namespace BTCPayServer.Tests
.RouteValues.First(); .RouteValues.First();
var invoiceId = Assert var invoiceId = Assert
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false)).Value .IsType<OkObjectResult>(
await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false)).Value
.ToString(); .ToString();
var actionResult = Assert var actionResult = Assert
.IsType<RedirectToActionResult>(await paymentRequestController.PayPaymentRequest(response.Value.ToString())); .IsType<RedirectToActionResult>(
await paymentRequestController.PayPaymentRequest(response.Value.ToString()));
Assert.Equal("Checkout", actionResult.ActionName); Assert.Equal("Checkout", actionResult.ActionName);
Assert.Equal("Invoice", actionResult.ControllerName); Assert.Equal("Invoice", actionResult.ControllerName);
Assert.Contains(actionResult.RouteValues, pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId); Assert.Contains(actionResult.RouteValues,
pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant); var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
Assert.Equal(1, invoice.Price); Assert.Equal(1, invoice.Price);
@ -138,8 +156,8 @@ namespace BTCPayServer.Tests
.RouteValues.First(); .RouteValues.First();
Assert Assert
.IsType<BadRequestObjectResult>(await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false)); .IsType<BadRequestObjectResult>(
await paymentRequestController.PayPaymentRequest(response.Value.ToString(), false));
} }
} }
@ -156,11 +174,9 @@ namespace BTCPayServer.Tests
var paymentRequestController = user.GetController<PaymentRequestController>(); var paymentRequestController = user.GetController<PaymentRequestController>();
Assert.IsType<NotFoundResult>(await Assert.IsType<NotFoundResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(Guid.NewGuid().ToString(), false)); paymentRequestController.CancelUnpaidPendingInvoice(Guid.NewGuid().ToString(), false));
var request = new UpdatePaymentRequestViewModel() var request = new UpdatePaymentRequestViewModel()
{ {
Title = "original juice", Title = "original juice",
@ -176,15 +192,18 @@ namespace BTCPayServer.Tests
var paymentRequestId = response.Value.ToString(); var paymentRequestId = response.Value.ToString();
var invoiceId = Assert var invoiceId = Assert
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false)).Value .IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false))
.Value
.ToString(); .ToString();
var actionResult = Assert var actionResult = Assert
.IsType<RedirectToActionResult>(await paymentRequestController.PayPaymentRequest(response.Value.ToString())); .IsType<RedirectToActionResult>(
await paymentRequestController.PayPaymentRequest(response.Value.ToString()));
Assert.Equal("Checkout", actionResult.ActionName); Assert.Equal("Checkout", actionResult.ActionName);
Assert.Equal("Invoice", actionResult.ControllerName); Assert.Equal("Invoice", actionResult.ControllerName);
Assert.Contains(actionResult.RouteValues, pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId); Assert.Contains(actionResult.RouteValues,
pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant); var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
Assert.Equal(InvoiceState.ToString(InvoiceStatus.New), invoice.Status); Assert.Equal(InvoiceState.ToString(InvoiceStatus.New), invoice.Status);
@ -194,11 +213,24 @@ namespace BTCPayServer.Tests
invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant); invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
Assert.Equal(InvoiceState.ToString(InvoiceStatus.Invalid), invoice.Status); Assert.Equal(InvoiceState.ToString(InvoiceStatus.Invalid), invoice.Status);
Assert.IsType<BadRequestObjectResult>(await Assert.IsType<BadRequestObjectResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false)); paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false));
invoiceId = Assert
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false))
.Value
.ToString();
invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
//a hack to generate invoices for the payment request is to manually create an invocie with an order id that matches:
user.BitPay.CreateInvoice(new Invoice(1, "USD")
{
OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(paymentRequestId)
});
//shouldnt crash
await paymentRequestController.ViewPaymentRequest(paymentRequestId);
await paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId);
} }
} }
} }

View file

@ -292,18 +292,21 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
} }
var invoice = result.Invoices.SingleOrDefault(requestInvoice => var invoices = result.Invoices.Where(requestInvoice =>
requestInvoice.Status.Equals(InvoiceState.ToString(InvoiceStatus.New), requestInvoice.Status.Equals(InvoiceState.ToString(InvoiceStatus.New),
StringComparison.InvariantCulture) && !requestInvoice.Payments.Any()); StringComparison.InvariantCulture) && !requestInvoice.Payments.Any());
if (invoice == null) if (!invoices.Any())
{ {
return BadRequest("No unpaid pending invoice to cancel"); return BadRequest("No unpaid pending invoice to cancel");
} }
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoice.Id); foreach (var invoice in invoices)
_EventAggregator.Publish(new InvoiceEvent(await _InvoiceRepository.GetInvoice(invoice.Id), 1008, {
InvoiceEvent.MarkedInvalid)); await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoice.Id);
_EventAggregator.Publish(new InvoiceEvent(await _InvoiceRepository.GetInvoice(invoice.Id), 1008,
InvoiceEvent.MarkedInvalid));
}
if (redirect) if (redirect)
{ {

View file

@ -80,7 +80,8 @@ namespace BTCPayServer.PaymentRequest
var paymentStats = _AppService.GetContributionsByPaymentMethodId(blob.Currency, invoices, true); var paymentStats = _AppService.GetContributionsByPaymentMethodId(blob.Currency, invoices, true);
var amountDue = blob.Amount - paymentStats.TotalCurrency; var amountDue = blob.Amount - paymentStats.TotalCurrency;
var pendingInvoice = invoices.SingleOrDefault(entity => entity.Status == InvoiceStatus.New); var pendingInvoice = invoices.OrderByDescending(entity => entity.InvoiceTime)
.FirstOrDefault(entity => entity.Status == InvoiceStatus.New);
return new ViewPaymentRequestViewModel(pr) return new ViewPaymentRequestViewModel(pr)
{ {