add test and refactor for PR

This commit is contained in:
Andrew Camilleri
2018-05-05 16:07:22 +02:00
parent b002c49dac
commit 9a92646d4d
3 changed files with 45 additions and 28 deletions

View File

@@ -229,6 +229,33 @@ namespace BTCPayServer.Tests
#pragma warning restore CS0618 #pragma warning restore CS0618
} }
[Fact]
public void CanAcceptInvoiceWithTolerance()
{
var entity = new InvoiceEntity();
#pragma warning disable CS0618
entity.Payments = new List<PaymentEntity>();
entity.SetPaymentMethod(new PaymentMethod() { CryptoCode = "BTC", Rate = 5000, TxFee = Money.Coins(0.1m) });
entity.ProductInformation = new ProductInformation() { Price = 5000 };
entity.PaymentTolerance = 0;
var paymentMethod = entity.GetPaymentMethods(null).TryGet("BTC", PaymentTypes.BTCLike);
var accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(1.1m), accounting.Due);
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
Assert.Equal(Money.Coins(1.1m), accounting.MinimumTotalDue);
entity.PaymentTolerance = 10;
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(0.99m), accounting.MinimumTotalDue);
entity.PaymentTolerance = 100;
accounting = paymentMethod.Calculate();
Assert.Equal(Money.Coins(0), accounting.MinimumTotalDue);
}
[Fact] [Fact]
public void CanPayUsingBIP70() public void CanPayUsingBIP70()
{ {

View File

@@ -76,16 +76,15 @@ namespace BTCPayServer.HostedServices
if (paymentMethod == null) if (paymentMethod == null)
return; return;
var network = _NetworkProvider.GetNetwork(paymentMethod.GetId().CryptoCode); var network = _NetworkProvider.GetNetwork(paymentMethod.GetId().CryptoCode);
var isPaid = accounting.IsPaid(invoice.PaymentTolerance, out var paidOver);
if (invoice.Status == "new" || invoice.Status == "expired") if (invoice.Status == "new" || invoice.Status == "expired")
{ {
if (isPaid) if (accounting.Paid >= accounting.MinimumTotalDue)
{ {
if (invoice.Status == "new") if (invoice.Status == "new")
{ {
context.Events.Add(new InvoiceEvent(invoice, 1003, "invoice_paidInFull")); context.Events.Add(new InvoiceEvent(invoice, 1003, "invoice_paidInFull"));
invoice.Status = "paid"; invoice.Status = "paid";
invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? "paidOver" : null; invoice.ExceptionStatus = accounting.Paid > accounting.MinimumTotalDue ? "paidOver" : null;
await _InvoiceRepository.UnaffectAddress(invoice.Id); await _InvoiceRepository.UnaffectAddress(invoice.Id);
context.MarkDirty(); context.MarkDirty();
} }
@@ -97,7 +96,7 @@ namespace BTCPayServer.HostedServices
} }
} }
if (!isPaid && invoice.ExceptionStatus != "paidPartial" && invoice.GetPayments().Count != 0) if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments().Count != 0 && invoice.ExceptionStatus != "paidPartial")
{ {
invoice.ExceptionStatus = "paidPartial"; invoice.ExceptionStatus = "paidPartial";
context.MarkDirty(); context.MarkDirty();
@@ -107,19 +106,19 @@ namespace BTCPayServer.HostedServices
// Just make sure RBF did not cancelled a payment // Just make sure RBF did not cancelled a payment
if (invoice.Status == "paid") if (invoice.Status == "paid")
{ {
if (!paidOver && invoice.ExceptionStatus == "paidOver") if (accounting.Paid == accounting.MinimumTotalDue && invoice.ExceptionStatus == "paidOver")
{ {
invoice.ExceptionStatus = null; invoice.ExceptionStatus = null;
context.MarkDirty(); context.MarkDirty();
} }
if (paidOver&& invoice.ExceptionStatus != "paidOver") if (accounting.Paid > accounting.MinimumTotalDue && invoice.ExceptionStatus != "paidOver")
{ {
invoice.ExceptionStatus = "paidOver"; invoice.ExceptionStatus = "paidOver";
context.MarkDirty(); context.MarkDirty();
} }
if (!isPaid) if (accounting.Paid < accounting.MinimumTotalDue)
{ {
invoice.Status = "new"; invoice.Status = "new";
invoice.ExceptionStatus = accounting.Paid == Money.Zero ? null : "paidPartial"; invoice.ExceptionStatus = accounting.Paid == Money.Zero ? null : "paidPartial";
@@ -130,19 +129,19 @@ namespace BTCPayServer.HostedServices
if (invoice.Status == "paid") if (invoice.Status == "paid")
{ {
var confirmedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy, network)); var confirmedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy, network));
var confirmedIsPaid = confirmedAccounting.IsPaid(invoice.PaymentTolerance, out var confirmedPaidOver);
if (// Is after the monitoring deadline if (// Is after the monitoring deadline
(invoice.MonitoringExpiration < DateTimeOffset.UtcNow) (invoice.MonitoringExpiration < DateTimeOffset.UtcNow)
&& &&
// And not enough amount confirmed // And not enough amount confirmed
(!confirmedIsPaid)) (confirmedAccounting.Paid < accounting.MinimumTotalDue))
{ {
await _InvoiceRepository.UnaffectAddress(invoice.Id); await _InvoiceRepository.UnaffectAddress(invoice.Id);
context.Events.Add(new InvoiceEvent(invoice, 1013, "invoice_failedToConfirm")); context.Events.Add(new InvoiceEvent(invoice, 1013, "invoice_failedToConfirm"));
invoice.Status = "invalid"; invoice.Status = "invalid";
context.MarkDirty(); context.MarkDirty();
} }
else if (confirmedIsPaid) else if (confirmedAccounting.Paid >= accounting.MinimumTotalDue)
{ {
await _InvoiceRepository.UnaffectAddress(invoice.Id); await _InvoiceRepository.UnaffectAddress(invoice.Id);
context.Events.Add(new InvoiceEvent(invoice, 1005, "invoice_confirmed")); context.Events.Add(new InvoiceEvent(invoice, 1005, "invoice_confirmed"));
@@ -154,8 +153,7 @@ namespace BTCPayServer.HostedServices
if (invoice.Status == "confirmed") if (invoice.Status == "confirmed")
{ {
var completedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentCompleted(p, network)); var completedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentCompleted(p, network));
var confirmedIsPaid = completedAccounting.IsPaid(invoice.PaymentTolerance, out var completedPaidOver); if (completedAccounting.Paid >= accounting.MinimumTotalDue)
if (confirmedIsPaid)
{ {
context.Events.Add(new InvoiceEvent(invoice, 1006, "invoice_completed")); context.Events.Add(new InvoiceEvent(invoice, 1006, "invoice_completed"));
invoice.Status = "complete"; invoice.Status = "complete";

View File

@@ -524,22 +524,10 @@ namespace BTCPayServer.Services.Invoices
/// Total amount of network fee to pay to the invoice /// Total amount of network fee to pay to the invoice
/// </summary> /// </summary>
public Money NetworkFee { get; set; } public Money NetworkFee { get; set; }
/// <summary>
public bool IsPaid(double tolerance, out bool paidOver) /// Minimum required to be paid in order to accept invocie as paid
{ /// </summary>
paidOver = false; public Money MinimumTotalDue { get; set; }
if (Paid < TotalDue)
{
var tolerantAmount = (TotalDue.Satoshi * (tolerance == 0 ? 1 : (tolerance / 100)));
var minimumTotalDue = TotalDue.Satoshi - tolerantAmount;
return Paid.Satoshi >= minimumTotalDue;
}else if (Paid > TotalDue)
{
paidOver = true;
}
return true;
}
} }
public class PaymentMethod public class PaymentMethod
@@ -688,6 +676,10 @@ namespace BTCPayServer.Services.Invoices
accounting.Due = Money.Max(accounting.TotalDue - accounting.Paid, Money.Zero); accounting.Due = Money.Max(accounting.TotalDue - accounting.Paid, Money.Zero);
accounting.DueUncapped = accounting.TotalDue - accounting.Paid; accounting.DueUncapped = accounting.TotalDue - accounting.Paid;
accounting.NetworkFee = accounting.TotalDue - totalDueNoNetworkCost; accounting.NetworkFee = accounting.TotalDue - totalDueNoNetworkCost;
accounting.MinimumTotalDue = new Money(Convert.ToInt32(Math.Ceiling(accounting.TotalDue.Satoshi -
(accounting.TotalDue.Satoshi *
(ParentEntity.PaymentTolerance / 100.0)
))));
return accounting; return accounting;
} }