start work on payment tolerance feature

This commit is contained in:
Andrew Camilleri
2018-05-04 16:15:34 +02:00
parent 8a4da361fd
commit c3d73236e0
9 changed files with 44 additions and 4 deletions

View File

@@ -98,6 +98,7 @@ namespace BTCPayServer.Controllers
entity.ExtendedNotifications = invoice.ExtendedNotifications; entity.ExtendedNotifications = invoice.ExtendedNotifications;
entity.NotificationURL = notificationUri?.AbsoluteUri; entity.NotificationURL = notificationUri?.AbsoluteUri;
entity.BuyerInformation = Map<Invoice, BuyerInformation>(invoice); entity.BuyerInformation = Map<Invoice, BuyerInformation>(invoice);
entity.PaymentTolerance = storeBlob.PaymentTolerance;
//Another way of passing buyer info to support //Another way of passing buyer info to support
FillBuyerInfo(invoice.Buyer, entity.BuyerInformation); FillBuyerInfo(invoice.Buyer, entity.BuyerInformation);
if (entity?.BuyerInformation?.BuyerEmail != null) if (entity?.BuyerInformation?.BuyerEmail != null)

View File

@@ -431,6 +431,7 @@ namespace BTCPayServer.Controllers
vm.MonitoringExpiration = storeBlob.MonitoringExpiration; vm.MonitoringExpiration = storeBlob.MonitoringExpiration;
vm.InvoiceExpiration = storeBlob.InvoiceExpiration; vm.InvoiceExpiration = storeBlob.InvoiceExpiration;
vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate; vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate;
vm.PaymentTolerance = storeBlob.PaymentTolerance;
return View(vm); return View(vm);
} }
@@ -496,6 +497,7 @@ namespace BTCPayServer.Controllers
blob.MonitoringExpiration = model.MonitoringExpiration; blob.MonitoringExpiration = model.MonitoringExpiration;
blob.InvoiceExpiration = model.InvoiceExpiration; blob.InvoiceExpiration = model.InvoiceExpiration;
blob.LightningDescriptionTemplate = model.LightningDescriptionTemplate ?? string.Empty; blob.LightningDescriptionTemplate = model.LightningDescriptionTemplate ?? string.Empty;
blob.PaymentTolerance = model.PaymentTolerance;
if (StoreData.SetStoreBlob(blob)) if (StoreData.SetStoreBlob(blob))
{ {

View File

@@ -247,6 +247,7 @@ namespace BTCPayServer.Data
{ {
InvoiceExpiration = 15; InvoiceExpiration = 15;
MonitoringExpiration = 60; MonitoringExpiration = 60;
PaymentTolerance = 0;
RequiresRefundEmail = true; RequiresRefundEmail = true;
} }
public bool NetworkFeeDisabled public bool NetworkFeeDisabled
@@ -326,6 +327,10 @@ namespace BTCPayServer.Data
} }
} }
[DefaultValue(0)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public double PaymentTolerance { get; set; }
public BTCPayServer.Rating.RateRules GetRateRules(BTCPayNetworkProvider networkProvider) public BTCPayServer.Rating.RateRules GetRateRules(BTCPayNetworkProvider networkProvider)
{ {
if (!RateScripting || if (!RateScripting ||

View File

@@ -312,6 +312,7 @@ namespace BTCPayServer.HostedServices
{ {
if (e.Name == "invoice_expired" || if (e.Name == "invoice_expired" ||
e.Name == "invoice_paidInFull" || e.Name == "invoice_paidInFull" ||
e.Name == "invoice_paidWithinTolerance" ||
e.Name == "invoice_failedToConfirm" || e.Name == "invoice_failedToConfirm" ||
e.Name == "invoice_markedInvalid" || e.Name == "invoice_markedInvalid" ||
e.Name == "invoice_failedToConfirm" || e.Name == "invoice_failedToConfirm" ||

View File

@@ -96,11 +96,28 @@ namespace BTCPayServer.HostedServices
} }
} }
if (accounting.Paid < accounting.TotalDue && invoice.GetPayments().Count != 0 && invoice.ExceptionStatus != "paidPartial") if (accounting.Paid < accounting.TotalDue && invoice.GetPayments().Count != 0)
{ {
invoice.ExceptionStatus = "paidPartial"; if (invoice.PaymentTolerance > 0)
context.MarkDirty(); {
var tolerantAmount = (accounting.TotalDue.Satoshi * (invoice.PaymentTolerance / 100));
var minimumTotalDue = accounting.TotalDue.Satoshi - tolerantAmount;
if (accounting.Paid.Satoshi >= minimumTotalDue)
{
context.Events.Add(new InvoiceEvent(invoice, 1003, "invoide_paidWithinTolerance"));
invoice.ExceptionStatus = "paidWithinTolerance";
invoice.Status = "paid";
}
}
if (invoice.ExceptionStatus != "paidPartial")
{
invoice.ExceptionStatus = "paidPartial";
context.MarkDirty();
}
} }
} }
// Just make sure RBF did not cancelled a payment // Just make sure RBF did not cancelled a payment

View File

@@ -178,7 +178,7 @@ namespace BTCPayServer.Models
} }
//"exceptionStatus":false //"exceptionStatus":false
//Can be `paidPartial`, `paidOver`, or false //Can be `paidPartial`, `paidOver`, `paidWithinTolerance` or false
[JsonProperty("exceptionStatus")] [JsonProperty("exceptionStatus")]
public JToken ExceptionStatus public JToken ExceptionStatus
{ {

View File

@@ -85,5 +85,13 @@ namespace BTCPayServer.Models.StoreViewModels
{ {
get; set; get; set;
} = new List<LightningNode>(); } = new List<LightningNode>();
[Display(Name = "Consider the invoice paid even if the paid amount is ... % less than expected")]
[Range(0, 100)]
public double PaymentTolerance
{
get;
set;
}
} }
} }

View File

@@ -314,6 +314,7 @@ namespace BTCPayServer.Services.Invoices
} }
public bool ExtendedNotifications { get; set; } public bool ExtendedNotifications { get; set; }
public List<InvoiceEventData> Events { get; internal set; } public List<InvoiceEventData> Events { get; internal set; }
public double PaymentTolerance { get; set; }
public bool IsExpired() public bool IsExpired()
{ {

View File

@@ -44,6 +44,11 @@
<input asp-for="MonitoringExpiration" class="form-control" /> <input asp-for="MonitoringExpiration" class="form-control" />
<span asp-validation-for="MonitoringExpiration" class="text-danger"></span> <span asp-validation-for="MonitoringExpiration" class="text-danger"></span>
</div> </div>
<div class="form-group">
<label asp-for="PaymentTolerance"></label>
<input asp-for="PaymentTolerance" class="form-control" />
<span asp-validation-for="PaymentTolerance" class="text-danger"></span>
</div>
<div class="form-group"> <div class="form-group">
<label asp-for="SpeedPolicy"></label> <label asp-for="SpeedPolicy"></label>
<select asp-for="SpeedPolicy" class="form-control"> <select asp-for="SpeedPolicy" class="form-control">