diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index ad7fa5847..8c56dc132 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -255,7 +255,7 @@ namespace BTCPayServer.Tests { tester.SimulateCallback(invoiceAddress); var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); - Assert.Equal("paidPartial", localInvoice.Status); + Assert.Equal("new", localInvoice.Status); Assert.Equal(firstPayment, localInvoice.BtcPaid); txFee = localInvoice.BtcDue - invoice.BtcDue; Assert.Equal("paidPartial", localInvoice.ExceptionStatus); @@ -311,7 +311,7 @@ namespace BTCPayServer.Tests { tester.SimulateCallback(invoiceAddress); var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); - Assert.Equal("paidOver", localInvoice.Status); + Assert.Equal("paid", localInvoice.Status); Assert.Equal(Money.Zero, localInvoice.BtcDue); Assert.Equal("paidOver", (string)((JValue)localInvoice.ExceptionStatus).Value); }); diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 270e3a962..1025bf314 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.0 - 1.0.0.20 + 1.0.0.21 diff --git a/BTCPayServer/Services/Invoices/InvoiceWatcher.cs b/BTCPayServer/Services/Invoices/InvoiceWatcher.cs index 98ab9f859..8a57e29d0 100644 --- a/BTCPayServer/Services/Invoices/InvoiceWatcher.cs +++ b/BTCPayServer/Services/Invoices/InvoiceWatcher.cs @@ -106,14 +106,14 @@ namespace BTCPayServer.Services.Invoices private async Task<(bool NeedSave, UTXOChanges Changes)> UpdateInvoice(UTXOChanges changes, InvoiceEntity invoice) { bool needSave = false; - - if(invoice.Status != "invalid" && invoice.ExpirationTime < DateTimeOffset.UtcNow && (invoice.Status == "new" || invoice.Status == "paidPartial")) + + if(invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow) { needSave = true; - invoice.Status = "invalid"; + invoice.Status = "expired"; } - if(invoice.Status == "invalid" || invoice.Status == "new" || invoice.Status == "paidPartial") + if(invoice.Status == "expired" || invoice.Status == "new" || invoice.Status == "invalid") { var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy); changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false); @@ -135,18 +135,19 @@ namespace BTCPayServer.Services.Invoices { var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false); invoice.Payments.Add(payment); - if(invoice.Status == "new") + if(invoice.Status == "expired") { - invoice.Status = "paidPartial"; + if(invoice.ExceptionStatus == null) + invoice.ExceptionStatus = "paidLate"; needSave = true; } } } - if(invoice.Status == "paidPartial") + if(invoice.Status == "new") { var totalPaid = invoice.Payments.Select(p => p.Output.Value).Sum(); - if(totalPaid == invoice.GetTotalCryptoDue()) + if(totalPaid >= invoice.GetTotalCryptoDue()) { invoice.Status = "paid"; if(invoice.FullNotifications) @@ -157,45 +158,37 @@ namespace BTCPayServer.Services.Invoices needSave = true; } - if(totalPaid > invoice.GetTotalCryptoDue()) + if(totalPaid > invoice.GetTotalCryptoDue() && invoice.ExceptionStatus != "paidOver") { - invoice.Status = "paidOver"; invoice.ExceptionStatus = "paidOver"; needSave = true; } - if(totalPaid < invoice.GetTotalCryptoDue() && invoice.ExceptionStatus == null) + if(totalPaid < invoice.GetTotalCryptoDue() && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial") { invoice.ExceptionStatus = "paidPartial"; needSave = true; } } - if(invoice.Status == "paid" || invoice.Status == "paidOver") + if(invoice.Status == "paid") { - var getTransactions = invoice.Payments.Select(o => o.Outpoint.Hash).Select(o => _ExplorerClient.GetTransactionAsync(o, _Cts.Token)).ToArray(); - await Task.WhenAll(getTransactions).ConfigureAwait(false); - var transactions = getTransactions.Select(c => c.GetAwaiter().GetResult()).ToArray(); - - bool confirmed = false; - var minConf = transactions.Select(t => t.Confirmations).Min(); + var transactions = await GetPaymentsWithTransaction(invoice); if(invoice.SpeedPolicy == SpeedPolicy.HighSpeed) { - if(minConf > 0) - confirmed = true; - else - confirmed = !transactions.Any(t => t.Transaction.RBF); + transactions = transactions.Where(t => !t.Transaction.Transaction.RBF); } else if(invoice.SpeedPolicy == SpeedPolicy.MediumSpeed) { - confirmed = minConf >= 1; + transactions = transactions.Where(t => t.Transaction.Confirmations >= 1); } else if(invoice.SpeedPolicy == SpeedPolicy.LowSpeed) { - confirmed = minConf >= 6; + transactions = transactions.Where(t => t.Transaction.Confirmations >= 6); } - if(confirmed) + var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum(); + if(totalConfirmed >= invoice.GetTotalCryptoDue()) { invoice.Status = "confirmed"; _NotificationManager.Notify(invoice); @@ -205,11 +198,10 @@ namespace BTCPayServer.Services.Invoices if(invoice.Status == "confirmed") { - var getTransactions = invoice.Payments.Select(o => o.Outpoint.Hash).Select(o => _ExplorerClient.GetTransactionAsync(o, _Cts.Token)).ToArray(); - await Task.WhenAll(getTransactions).ConfigureAwait(false); - var transactions = getTransactions.Select(c => c.GetAwaiter().GetResult()).ToArray(); - var minConf = transactions.Select(t => t.Confirmations).Min(); - if(minConf >= 6) + var transactions = await GetPaymentsWithTransaction(invoice); + transactions = transactions.Where(t => t.Transaction.Confirmations >= 6); + var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum(); + if(totalConfirmed >= invoice.GetTotalCryptoDue()) { invoice.Status = "complete"; if(invoice.FullNotifications) @@ -221,6 +213,15 @@ namespace BTCPayServer.Services.Invoices return (needSave, changes); } + private async Task> GetPaymentsWithTransaction(InvoiceEntity invoice) + { + var getPayments = invoice.Payments + .Select(async o => (Payment: o, Transaction: await _ExplorerClient.GetTransactionAsync(o.Outpoint.Hash, _Cts.Token))) + .ToArray(); + await Task.WhenAll(getPayments).ConfigureAwait(false); + var transactions = getPayments.Select(c => (Payment: c.Result.Payment, Transaction: c.Result.Transaction)); + return transactions; + } TimeSpan _PollInterval; public TimeSpan PollInterval diff --git a/BTCPayServer/wwwroot/js/core.js b/BTCPayServer/wwwroot/js/core.js index 5f37967fd..8175aff99 100644 --- a/BTCPayServer/wwwroot/js/core.js +++ b/BTCPayServer/wwwroot/js/core.js @@ -166,7 +166,6 @@ function onDataCallback(jsonData) { window.parent.postMessage({ "invoiceId": srvModel.invoiceId, "status": newStatus }, "*"); } if (newStatus == "complete" || - newStatus == "paidOver" || newStatus == "confirmed" || newStatus == "paid") { if ($(".modal-dialog").hasClass("expired")) { @@ -192,7 +191,7 @@ function onDataCallback(jsonData) { $("#paid").addClass("active"); } - if (newStatus == "invalid") { + if (newStatus == "expired" || newStatus == "invalid") { //TODO: different state if the invoice is invalid (failed to confirm after timeout) $(".timer-row").removeClass("expiring-soon"); $(".timer-row__message span").html("Invoice expired."); $(".timer-row__spinner").html("");