diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 88fad8c2c..4fdee81d6 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -256,6 +256,46 @@ namespace BTCPayServer.Tests } + [Fact] + public void CanAcceptInvoiceWithTolerance2() + { + using (var tester = ServerTester.Create()) + { + tester.Start(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterDerivationScheme("BTC"); + + // Set tolerance to 50% + var stores = user.GetController(); + var vm = Assert.IsType(Assert.IsType(stores.UpdateStore()).Model); + Assert.Equal(0.0, vm.PaymentTolerance); + vm.PaymentTolerance = 50.0; + Assert.IsType(stores.UpdateStore(vm).Result); + + var invoice = user.BitPay.CreateInvoice(new Invoice() + { + Buyer = new Buyer() { email = "test@fwf.com" }, + Price = 5000.0, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + + // Pays 75% + var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network); + tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Satoshis((decimal)invoice.BtcDue.Satoshi * 0.75m)); + + Eventually(() => + { + var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); + Assert.Equal("paid", localInvoice.Status); + }); + } + } + [Fact] public void CanPayUsingBIP70() { diff --git a/BTCPayServer/HostedServices/InvoiceWatcher.cs b/BTCPayServer/HostedServices/InvoiceWatcher.cs index b72d23b08..3c338a2d5 100644 --- a/BTCPayServer/HostedServices/InvoiceWatcher.cs +++ b/BTCPayServer/HostedServices/InvoiceWatcher.cs @@ -68,6 +68,8 @@ namespace BTCPayServer.HostedServices context.Events.Add(new InvoiceEvent(invoice, 1004, "invoice_expired")); invoice.Status = "expired"; + if(invoice.ExceptionStatus == "paidPartial") + context.Events.Add(new InvoiceEvent(invoice, 2000, "invoice_expiredPaidPartial")); } var payments = invoice.GetPayments().Where(p => p.Accounted).ToArray(); @@ -84,7 +86,7 @@ namespace BTCPayServer.HostedServices { context.Events.Add(new InvoiceEvent(invoice, 1003, "invoice_paidInFull")); invoice.Status = "paid"; - invoice.ExceptionStatus = accounting.Paid > accounting.MinimumTotalDue ? "paidOver" : null; + invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? "paidOver" : null; await _InvoiceRepository.UnaffectAddress(invoice.Id); context.MarkDirty(); } @@ -106,13 +108,13 @@ namespace BTCPayServer.HostedServices // Just make sure RBF did not cancelled a payment if (invoice.Status == "paid") { - if (accounting.Paid == accounting.MinimumTotalDue && invoice.ExceptionStatus == "paidOver") + if (accounting.MinimumTotalDue <= accounting.Paid && accounting.Paid <= accounting.TotalDue && invoice.ExceptionStatus == "paidOver") { invoice.ExceptionStatus = null; context.MarkDirty(); } - if (accounting.Paid > accounting.MinimumTotalDue && invoice.ExceptionStatus != "paidOver") + if (accounting.Paid > accounting.TotalDue && invoice.ExceptionStatus != "paidOver") { invoice.ExceptionStatus = "paidOver"; context.MarkDirty(); diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index 80fdb1256..e6bcee054 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -676,10 +676,7 @@ namespace BTCPayServer.Services.Invoices accounting.Due = Money.Max(accounting.TotalDue - accounting.Paid, Money.Zero); accounting.DueUncapped = accounting.TotalDue - accounting.Paid; accounting.NetworkFee = accounting.TotalDue - totalDueNoNetworkCost; - accounting.MinimumTotalDue = new Money(Convert.ToInt32(Math.Ceiling(accounting.TotalDue.Satoshi - - (accounting.TotalDue.Satoshi * - (ParentEntity.PaymentTolerance / 100.0) - )))); + accounting.MinimumTotalDue = Money.Satoshis(accounting.TotalDue.Satoshi * (1.0m - ((decimal)ParentEntity.PaymentTolerance / 100.0m))); return accounting; }