mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 09:54:30 +01:00
Refactor how invoice payments are computed
This commit is contained in:
parent
a37fdde214
commit
a863812f90
@ -47,28 +47,34 @@ namespace BTCPayServer.Tests
|
||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||
|
||||
Assert.Equal(Money.Coins(1.1m), cryptoData.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.1m), cryptoData.GetTotalCryptoDue());
|
||||
var accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Coins(1.1m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()), Accounted = true });
|
||||
|
||||
accounting = cryptoData.Calculate();
|
||||
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
|
||||
Assert.Equal(Money.Coins(0.7m), cryptoData.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.2m), cryptoData.GetTotalCryptoDue());
|
||||
Assert.Equal(Money.Coins(0.7m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.2m), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
||||
Assert.Equal(Money.Coins(0.6m), cryptoData.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), cryptoData.GetTotalCryptoDue());
|
||||
|
||||
accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Coins(0.6m), accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()), Accounted = true });
|
||||
|
||||
Assert.Equal(Money.Zero, cryptoData.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), cryptoData.GetTotalCryptoDue());
|
||||
accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
||||
|
||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
||||
|
||||
Assert.Equal(Money.Zero, cryptoData.GetCryptoDue());
|
||||
Assert.Equal(Money.Coins(1.3m), cryptoData.GetTotalCryptoDue());
|
||||
accounting = cryptoData.Calculate();
|
||||
Assert.Equal(Money.Zero, accounting.Due);
|
||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,8 @@ namespace BTCPayServer.Controllers
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var cryptoInfo = dto.CryptoInfo.First(o => o.CryptoCode.Equals(cryptoCode, StringComparison.OrdinalIgnoreCase));
|
||||
var store = await _StoreRepository.FindStore(invoice.StoreId);
|
||||
|
||||
var accounting = cryptoData.Calculate();
|
||||
InvoiceDetailsModel model = new InvoiceDetailsModel()
|
||||
{
|
||||
StoreName = store.StoreName,
|
||||
@ -75,10 +77,10 @@ namespace BTCPayServer.Controllers
|
||||
BuyerInformation = invoice.BuyerInformation,
|
||||
Rate = cryptoData.Rate,
|
||||
Fiat = dto.Price + " " + dto.Currency,
|
||||
BTC = cryptoData.GetTotalCryptoDue().ToString() + $" {network.CryptoCode}",
|
||||
BTCDue = cryptoData.GetCryptoDue().ToString() + $" {network.CryptoCode}",
|
||||
BTCPaid = cryptoData.GetTotalPaid().ToString() + $" {network.CryptoCode}",
|
||||
NetworkFee = cryptoData.GetNetworkFee().ToString() + $" {network.CryptoCode}",
|
||||
BTC = accounting.TotalDue.ToString() + $" {network.CryptoCode}",
|
||||
BTCDue = accounting.Due.ToString() + $" {network.CryptoCode}",
|
||||
BTCPaid = accounting.Paid.ToString() + $" {network.CryptoCode}",
|
||||
NetworkFee = accounting.NetworkFee.ToString() + $" {network.CryptoCode}",
|
||||
NotificationUrl = invoice.NotificationURL,
|
||||
ProductInformation = invoice.ProductInformation,
|
||||
BitcoinAddress = BitcoinAddress.Create(cryptoInfo.Address, network.NBitcoinNetwork),
|
||||
@ -137,15 +139,16 @@ namespace BTCPayServer.Controllers
|
||||
var dto = invoice.EntityToDTO(_NetworkProvider);
|
||||
var cryptoInfo = dto.CryptoInfo.First(o => o.CryptoCode == network.CryptoCode);
|
||||
var currency = invoice.ProductInformation.Currency;
|
||||
var accounting = cryptoData.Calculate();
|
||||
var model = new PaymentModel()
|
||||
{
|
||||
ServerUrl = HttpContext.Request.GetAbsoluteRoot(),
|
||||
OrderId = invoice.OrderId,
|
||||
InvoiceId = invoice.Id,
|
||||
BtcAddress = cryptoData.DepositAddress,
|
||||
BtcAmount = (cryptoData.GetTotalCryptoDue() - cryptoData.TxFee).ToString(),
|
||||
BtcTotalDue = cryptoData.GetTotalCryptoDue().ToString(),
|
||||
BtcDue = cryptoData.GetCryptoDue().ToString(),
|
||||
BtcAmount = (accounting.TotalDue - cryptoData.TxFee).ToString(),
|
||||
BtcTotalDue = accounting.TotalDue.ToString(),
|
||||
BtcDue = accounting.Due.ToString(),
|
||||
CustomerEmail = invoice.RefundMail,
|
||||
ExpirationSeconds = Math.Max(0, (int)(invoice.ExpirationTime - DateTimeOffset.UtcNow).TotalSeconds),
|
||||
MaxTimeSeconds = (int)(invoice.ExpirationTime - invoice.InvoiceTime).TotalSeconds,
|
||||
@ -155,8 +158,8 @@ namespace BTCPayServer.Controllers
|
||||
StoreName = store.StoreName,
|
||||
TxFees = cryptoData.TxFee.ToString(),
|
||||
InvoiceBitcoinUrl = cryptoInfo.PaymentUrls.BIP72,
|
||||
TxCount = cryptoData.GetTxCount(),
|
||||
BtcPaid = cryptoData.GetTotalPaid().ToString(),
|
||||
TxCount = accounting.TxCount,
|
||||
BtcPaid = accounting.Paid.ToString(),
|
||||
Status = invoice.Status
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Newtonsoft.Json;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -66,18 +67,49 @@ namespace BTCPayServer.Models
|
||||
}
|
||||
|
||||
//"btcDue":"0.001160"
|
||||
/// <summary>
|
||||
/// Amount of crypto remaining to pay this invoice
|
||||
/// </summary>
|
||||
[JsonProperty("due")]
|
||||
public string Due
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[JsonProperty("paymentUrls")]
|
||||
public NBitpayClient.InvoicePaymentUrls PaymentUrls
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[JsonProperty("address")]
|
||||
public string Address { get; set; }
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of this invoice
|
||||
/// </summary>
|
||||
[JsonProperty("totalDue")]
|
||||
public string TotalDue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of network fee to pay to the invoice
|
||||
/// </summary>
|
||||
[JsonProperty("networkFee")]
|
||||
public string NetworkFee { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of transactions required to pay
|
||||
/// </summary>
|
||||
[JsonProperty("txCount")]
|
||||
public int TxCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of the invoice paid in this crypto
|
||||
/// </summary>
|
||||
[JsonProperty("cryptoPaid")]
|
||||
public Money CryptoPaid { get; set; }
|
||||
}
|
||||
|
||||
//{"facade":"pos/invoice","data":{,}}
|
||||
|
@ -255,13 +255,19 @@ namespace BTCPayServer.Services.Invoices
|
||||
dto.CryptoInfo = new List<InvoiceCryptoInfo>();
|
||||
foreach (var info in this.GetCryptoData().Values)
|
||||
{
|
||||
var accounting = info.Calculate();
|
||||
var cryptoInfo = new InvoiceCryptoInfo();
|
||||
cryptoInfo.CryptoCode = info.CryptoCode;
|
||||
cryptoInfo.Rate = info.Rate;
|
||||
cryptoInfo.Price = Money.Coins(ProductInformation.Price / cryptoInfo.Rate).ToString();
|
||||
cryptoInfo.Due = info.GetCryptoDue().ToString();
|
||||
var paid = Payments.Where(p => p.Accounted && p.GetCryptoCode() == info.CryptoCode).Select(p => p.GetValue()).Sum();
|
||||
cryptoInfo.Paid = paid.ToString();
|
||||
|
||||
cryptoInfo.Due = accounting.Due.ToString();
|
||||
cryptoInfo.Paid = accounting.Paid.ToString();
|
||||
cryptoInfo.TotalDue = accounting.TotalDue.ToString();
|
||||
cryptoInfo.NetworkFee = accounting.NetworkFee.ToString();
|
||||
cryptoInfo.TxCount = accounting.TxCount;
|
||||
cryptoInfo.CryptoPaid = accounting.CryptoPaid;
|
||||
|
||||
cryptoInfo.Address = info.DepositAddress;
|
||||
cryptoInfo.ExRates = new Dictionary<string, double>
|
||||
{
|
||||
@ -372,6 +378,38 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
}
|
||||
|
||||
public class CryptoDataAccounting
|
||||
{
|
||||
/// <summary>
|
||||
/// Total amount of this invoice
|
||||
/// </summary>
|
||||
public Money TotalDue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Amount of crypto remaining to pay this invoice
|
||||
/// </summary>
|
||||
public Money Due { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of the invoice paid after conversion to this crypto currency
|
||||
/// </summary>
|
||||
public Money Paid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of the invoice paid in this currency
|
||||
/// </summary>
|
||||
public Money CryptoPaid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of transactions required to pay
|
||||
/// </summary>
|
||||
public int TxCount { get; set; }
|
||||
/// <summary>
|
||||
/// Total amount of network fee to pay to the invoice
|
||||
/// </summary>
|
||||
public Money NetworkFee { get; set; }
|
||||
}
|
||||
|
||||
public class CryptoData
|
||||
{
|
||||
[JsonIgnore]
|
||||
@ -386,28 +424,13 @@ namespace BTCPayServer.Services.Invoices
|
||||
public Money TxFee { get; set; }
|
||||
[JsonProperty(PropertyName = "depositAddress")]
|
||||
public string DepositAddress { get; set; }
|
||||
|
||||
public Money GetNetworkFee()
|
||||
{
|
||||
var item = Calculate();
|
||||
return TxFee * item.TxCount;
|
||||
}
|
||||
|
||||
public int GetTxCount()
|
||||
{
|
||||
return Calculate().TxCount;
|
||||
}
|
||||
|
||||
public Money GetTotalCryptoDue()
|
||||
{
|
||||
return Calculate().TotalDue;
|
||||
}
|
||||
|
||||
private (Money TotalDue, Money Paid, int TxCount) Calculate()
|
||||
|
||||
public CryptoDataAccounting Calculate()
|
||||
{
|
||||
var cryptoData = ParentEntity.GetCryptoData();
|
||||
var totalDue = Money.Coins(ParentEntity.ProductInformation.Price / Rate) + TxFee;
|
||||
var paid = Money.Zero;
|
||||
var cryptoPaid = Money.Zero;
|
||||
int txCount = 1;
|
||||
var payments =
|
||||
ParentEntity.Payments
|
||||
@ -416,6 +439,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
.Select(_ =>
|
||||
{
|
||||
paid += _.GetValue(cryptoData, CryptoCode);
|
||||
if (CryptoCode == _.GetCryptoCode())
|
||||
cryptoPaid += _.GetValue();
|
||||
return _;
|
||||
})
|
||||
.TakeWhile(_ =>
|
||||
@ -429,19 +454,17 @@ namespace BTCPayServer.Services.Invoices
|
||||
return !paidEnough;
|
||||
})
|
||||
.ToArray();
|
||||
return (totalDue, paid, txCount);
|
||||
}
|
||||
|
||||
public Money GetTotalPaid()
|
||||
{
|
||||
return Calculate().Paid;
|
||||
}
|
||||
public Money GetCryptoDue()
|
||||
{
|
||||
var o = Calculate();
|
||||
var v = o.TotalDue - o.Paid;
|
||||
return v < Money.Zero ? Money.Zero : v;
|
||||
var accounting = new CryptoDataAccounting();
|
||||
accounting.TotalDue = totalDue;
|
||||
accounting.Paid = paid;
|
||||
accounting.TxCount = txCount;
|
||||
accounting.CryptoPaid = cryptoPaid;
|
||||
accounting.Due = Money.Max(accounting.TotalDue - accounting.Paid, Money.Zero);
|
||||
accounting.NetworkFee = TxFee * txCount;
|
||||
return accounting;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class AccountedPaymentEntity
|
||||
|
@ -144,7 +144,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
Assigned = DateTimeOffset.UtcNow
|
||||
});
|
||||
textSearch.Add(cryptoData.DepositAddress);
|
||||
textSearch.Add(cryptoData.GetTotalCryptoDue().ToString());
|
||||
textSearch.Add(cryptoData.Calculate().TotalDue.ToString());
|
||||
}
|
||||
context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoice.Id });
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
@ -148,6 +148,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
var network = _NetworkProvider.GetNetwork("BTC");
|
||||
var cryptoData = invoice.GetCryptoData(network);
|
||||
var cryptoDataAll = invoice.GetCryptoData();
|
||||
var accounting = cryptoData.Calculate();
|
||||
if (invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow)
|
||||
{
|
||||
needSave = true;
|
||||
@ -160,7 +161,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
if (invoice.Status == "new" || invoice.Status == "expired")
|
||||
{
|
||||
var totalPaid = (await GetPaymentsWithTransaction(invoice)).Select(p => p.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||
if (totalPaid >= cryptoData.GetTotalCryptoDue())
|
||||
if (totalPaid >= accounting.TotalDue)
|
||||
{
|
||||
if (invoice.Status == "new")
|
||||
{
|
||||
@ -177,14 +178,14 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
}
|
||||
|
||||
if (totalPaid > cryptoData.GetTotalCryptoDue() && invoice.ExceptionStatus != "paidOver")
|
||||
if (totalPaid > accounting.TotalDue && invoice.ExceptionStatus != "paidOver")
|
||||
{
|
||||
invoice.ExceptionStatus = "paidOver";
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
needSave = true;
|
||||
}
|
||||
|
||||
if (totalPaid < cryptoData.GetTotalCryptoDue() && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial")
|
||||
if (totalPaid < accounting.TotalDue && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial")
|
||||
{
|
||||
Logs.PayServer.LogInformation("Paid to " + cryptoData.DepositAddress);
|
||||
invoice.ExceptionStatus = "paidPartial";
|
||||
@ -221,7 +222,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
(invoice.MonitoringExpiration < DateTimeOffset.UtcNow)
|
||||
&&
|
||||
// And not enough amount confirmed
|
||||
(chainTotalConfirmed < cryptoData.GetTotalCryptoDue()))
|
||||
(chainTotalConfirmed < accounting.TotalDue))
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoiceStatusChangedEvent(invoice, "invalid")));
|
||||
@ -231,7 +232,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
else
|
||||
{
|
||||
var totalConfirmed = transactions.Select(t => t.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||
if (totalConfirmed >= cryptoData.GetTotalCryptoDue())
|
||||
if (totalConfirmed >= accounting.TotalDue)
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoiceStatusChangedEvent(invoice, "confirmed")));
|
||||
@ -246,7 +247,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
var transactions = await GetPaymentsWithTransaction(invoice);
|
||||
transactions = transactions.Where(t => t.Confirmations >= 6);
|
||||
var totalConfirmed = transactions.Select(t => t.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||
if (totalConfirmed >= cryptoData.GetTotalCryptoDue())
|
||||
if (totalConfirmed >= accounting.TotalDue)
|
||||
{
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoiceStatusChangedEvent(invoice, "complete")));
|
||||
invoice.Status = "complete";
|
||||
|
Loading…
Reference in New Issue
Block a user