mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-09 16:04:43 +01:00
Refactor logic for calculating due amount of invoices (#5174)
* Refactor logic for calculating due amount of invoices * Remove Money type from the accounting * Fix tests * Fix a corner case * fix bug * Rename PaymentCurrency to Currency * Fix bug * Rename PaymentCurrency -> Currency * Payment objects should have access to the InvoiceEntity * Set Currency USD in tests * Simplify some code * Remove useless code * Simplify code, kukks comment
This commit is contained in:
parent
a7def63137
commit
22435a2bf5
39 changed files with 487 additions and 424 deletions
|
@ -1,4 +1,5 @@
|
||||||
#if ALTCOINS
|
#if ALTCOINS
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BTCPayServer.Common;
|
using BTCPayServer.Common;
|
||||||
|
@ -34,12 +35,12 @@ namespace BTCPayServer
|
||||||
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId));
|
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
|
public override PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, decimal? cryptoInfoDue)
|
||||||
{
|
{
|
||||||
//precision 0: 10 = 0.00000010
|
//precision 0: 10 = 0.00000010
|
||||||
//precision 2: 10 = 0.00001000
|
//precision 2: 10 = 0.00001000
|
||||||
//precision 8: 10 = 10
|
//precision 8: 10 = 10
|
||||||
var money = cryptoInfoDue is null ? null : new Money(cryptoInfoDue.ToDecimal(MoneyUnit.BTC) / decimal.Parse("1".PadRight(1 + 8 - Divisibility, '0')), MoneyUnit.BTC);
|
var money = cryptoInfoDue / (decimal)Math.Pow(10, 8 - Divisibility);
|
||||||
var builder = base.GenerateBIP21(cryptoInfoAddress, money);
|
var builder = base.GenerateBIP21(cryptoInfoAddress, money);
|
||||||
builder.QueryParams.Add("assetid", AssetId.ToString());
|
builder.QueryParams.Add("assetid", AssetId.ToString());
|
||||||
return builder;
|
return builder;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BTCPayServer.Common;
|
using BTCPayServer.Common;
|
||||||
|
@ -87,13 +88,13 @@ namespace BTCPayServer
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
|
public virtual PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, decimal? cryptoInfoDue)
|
||||||
{
|
{
|
||||||
var builder = new PaymentUrlBuilder(this.NBitcoinNetwork.UriScheme);
|
var builder = new PaymentUrlBuilder(this.NBitcoinNetwork.UriScheme);
|
||||||
builder.Host = cryptoInfoAddress;
|
builder.Host = cryptoInfoAddress;
|
||||||
if (cryptoInfoDue != null && cryptoInfoDue != Money.Zero)
|
if (cryptoInfoDue is not null && cryptoInfoDue.Value != 0.0m)
|
||||||
{
|
{
|
||||||
builder.QueryParams.Add("amount", cryptoInfoDue.ToString(false, true));
|
builder.QueryParams.Add("amount", cryptoInfoDue.Value.ToString(CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
|
@ -346,165 +346,213 @@ namespace BTCPayServer.Tests
|
||||||
Assert.True(Torrc.TryParse(input, out torrc));
|
Assert.True(Torrc.TryParse(input, out torrc));
|
||||||
Assert.Equal(expected, torrc.ToString());
|
Assert.Equal(expected, torrc.ToString());
|
||||||
}
|
}
|
||||||
|
[Fact]
|
||||||
|
public void CanCalculateDust()
|
||||||
|
{
|
||||||
|
var entity = new InvoiceEntity() { Currency = "USD" };
|
||||||
|
entity.Networks = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||||
|
entity.SetPaymentMethod(new PaymentMethod()
|
||||||
|
{
|
||||||
|
Currency = "BTC",
|
||||||
|
Rate = 34_000m
|
||||||
|
});
|
||||||
|
entity.Price = 4000;
|
||||||
|
entity.UpdateTotals();
|
||||||
|
var accounting = entity.GetPaymentMethods().First().Calculate();
|
||||||
|
// Exact price should be 0.117647059..., but the payment method round up to one sat
|
||||||
|
Assert.Equal(0.11764706m, accounting.Due);
|
||||||
|
entity.Payments.Add(new PaymentEntity()
|
||||||
|
{
|
||||||
|
Currency = "BTC",
|
||||||
|
Output = new TxOut(Money.Coins(0.11764706m), new Key()),
|
||||||
|
Accounted = true
|
||||||
|
});
|
||||||
|
entity.UpdateTotals();
|
||||||
|
Assert.Equal(0.0m, entity.NetDue);
|
||||||
|
// The dust's value is below 1 sat
|
||||||
|
Assert.True(entity.Dust > 0.0m);
|
||||||
|
Assert.True(Money.Satoshis(1.0m).ToDecimal(MoneyUnit.BTC) * entity.Rates["BTC"] > entity.Dust);
|
||||||
|
Assert.True(!entity.IsOverPaid);
|
||||||
|
Assert.True(!entity.IsUnderPaid);
|
||||||
|
|
||||||
|
// Now, imagine there is litecoin. It might seem from its
|
||||||
|
// perspecitve that there has been a slight over payment.
|
||||||
|
// However, Calculate() should just cap it to 0.0m
|
||||||
|
entity.SetPaymentMethod(new PaymentMethod()
|
||||||
|
{
|
||||||
|
Currency = "LTC",
|
||||||
|
Rate = 3400m
|
||||||
|
});
|
||||||
|
entity.UpdateTotals();
|
||||||
|
var method = entity.GetPaymentMethods().First(p => p.Currency == "LTC");
|
||||||
|
accounting = method.Calculate();
|
||||||
|
Assert.Equal(0.0m, accounting.DueUncapped);
|
||||||
|
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
}
|
||||||
#if ALTCOINS
|
#if ALTCOINS
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanCalculateCryptoDue()
|
public void CanCalculateCryptoDue()
|
||||||
{
|
{
|
||||||
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
var entity = new InvoiceEntity() { Currency = "USD" };
|
||||||
{
|
|
||||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null, null),
|
|
||||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
|
|
||||||
});
|
|
||||||
var entity = new InvoiceEntity();
|
|
||||||
entity.Networks = networkProvider;
|
entity.Networks = networkProvider;
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||||
entity.SetPaymentMethod(new PaymentMethod()
|
entity.SetPaymentMethod(new PaymentMethod()
|
||||||
{
|
{
|
||||||
CryptoCode = "BTC",
|
Currency = "BTC",
|
||||||
Rate = 5000,
|
Rate = 5000,
|
||||||
NextNetworkFee = Money.Coins(0.1m)
|
NextNetworkFee = Money.Coins(0.1m)
|
||||||
});
|
});
|
||||||
entity.Price = 5000;
|
entity.Price = 5000;
|
||||||
|
entity.UpdateTotals();
|
||||||
|
|
||||||
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
||||||
var accounting = paymentMethod.Calculate();
|
var accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Coins(1.1m), accounting.Due);
|
Assert.Equal(1.0m, accounting.ToSmallestUnit(Money.Satoshis(1.0m).ToDecimal(MoneyUnit.BTC)));
|
||||||
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
|
Assert.Equal(1.1m, accounting.Due);
|
||||||
|
Assert.Equal(1.1m, accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
|
Currency = "BTC",
|
||||||
Output = new TxOut(Money.Coins(0.5m), new Key()),
|
Output = new TxOut(Money.Coins(0.5m), new Key()),
|
||||||
|
Rate = 5000,
|
||||||
Accounted = true,
|
Accounted = true,
|
||||||
NetworkFee = 0.1m
|
NetworkFee = 0.1m
|
||||||
});
|
});
|
||||||
|
entity.UpdateTotals();
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
|
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
|
||||||
Assert.Equal(Money.Coins(0.7m), accounting.Due);
|
Assert.Equal(0.7m, accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.2m), accounting.TotalDue);
|
Assert.Equal(1.2m, accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
|
Currency = "BTC",
|
||||||
Output = new TxOut(Money.Coins(0.2m), new Key()),
|
Output = new TxOut(Money.Coins(0.2m), new Key()),
|
||||||
Accounted = true,
|
Accounted = true,
|
||||||
NetworkFee = 0.1m
|
NetworkFee = 0.1m
|
||||||
});
|
});
|
||||||
|
entity.UpdateTotals();
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Coins(0.6m), accounting.Due);
|
Assert.Equal(0.6m, accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
Assert.Equal(1.3m, accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
|
Currency = "BTC",
|
||||||
Output = new TxOut(Money.Coins(0.6m), new Key()),
|
Output = new TxOut(Money.Coins(0.6m), new Key()),
|
||||||
Accounted = true,
|
Accounted = true,
|
||||||
NetworkFee = 0.1m
|
NetworkFee = 0.1m
|
||||||
});
|
});
|
||||||
|
entity.UpdateTotals();
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Zero, accounting.Due);
|
Assert.Equal(0.0m, accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
Assert.Equal(1.3m, accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(
|
entity.Payments.Add(
|
||||||
new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
new PaymentEntity() { Currency = "BTC", Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
||||||
|
entity.UpdateTotals();
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Zero, accounting.Due);
|
Assert.Equal(0.0m, accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
Assert.Equal(1.3m, accounting.TotalDue);
|
||||||
|
|
||||||
entity = new InvoiceEntity();
|
entity = new InvoiceEntity();
|
||||||
entity.Networks = networkProvider;
|
entity.Networks = networkProvider;
|
||||||
entity.Price = 5000;
|
entity.Price = 5000;
|
||||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||||
paymentMethods.Add(
|
paymentMethods.Add(
|
||||||
new PaymentMethod() { CryptoCode = "BTC", Rate = 1000, NextNetworkFee = Money.Coins(0.1m) });
|
new PaymentMethod() { Currency = "BTC", Rate = 1000, NextNetworkFee = Money.Coins(0.1m) });
|
||||||
paymentMethods.Add(
|
paymentMethods.Add(
|
||||||
new PaymentMethod() { CryptoCode = "LTC", Rate = 500, NextNetworkFee = Money.Coins(0.01m) });
|
new PaymentMethod() { Currency = "LTC", Rate = 500, NextNetworkFee = Money.Coins(0.01m) });
|
||||||
entity.SetPaymentMethods(paymentMethods);
|
entity.SetPaymentMethods(paymentMethods);
|
||||||
entity.Payments = new List<PaymentEntity>();
|
entity.Payments = new List<PaymentEntity>();
|
||||||
|
entity.UpdateTotals();
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Coins(5.1m), accounting.Due);
|
Assert.Equal(5.1m, accounting.Due);
|
||||||
|
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
|
|
||||||
Assert.Equal(Money.Coins(10.01m), accounting.TotalDue);
|
Assert.Equal(10.01m, accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
CryptoCode = "BTC",
|
Currency = "BTC",
|
||||||
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
||||||
Accounted = true,
|
Accounted = true,
|
||||||
NetworkFee = 0.1m
|
NetworkFee = 0.1m
|
||||||
});
|
});
|
||||||
|
entity.UpdateTotals();
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Coins(4.2m), accounting.Due);
|
Assert.Equal(4.2m, accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||||
Assert.Equal(Money.Coins(1.0m), accounting.Paid);
|
Assert.Equal(1.0m, accounting.Paid);
|
||||||
Assert.Equal(Money.Coins(5.2m), accounting.TotalDue);
|
Assert.Equal(5.2m, accounting.TotalDue);
|
||||||
Assert.Equal(2, accounting.TxRequired);
|
Assert.Equal(2, accounting.TxRequired);
|
||||||
|
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 - 2.0m /* 8.21m */), accounting.Due);
|
Assert.Equal(10.01m + 0.1m * 2 - 2.0m /* 8.21m */, accounting.Due);
|
||||||
Assert.Equal(Money.Coins(0.0m), accounting.CryptoPaid);
|
Assert.Equal(0.0m, accounting.CryptoPaid);
|
||||||
Assert.Equal(Money.Coins(2.0m), accounting.Paid);
|
Assert.Equal(2.0m, accounting.Paid);
|
||||||
Assert.Equal(Money.Coins(10.01m + 0.1m * 2), accounting.TotalDue);
|
Assert.Equal(10.01m + 0.1m * 2, accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
CryptoCode = "LTC",
|
Currency = "LTC",
|
||||||
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
Output = new TxOut(Money.Coins(1.0m), new Key()),
|
||||||
Accounted = true,
|
Accounted = true,
|
||||||
NetworkFee = 0.01m
|
NetworkFee = 0.01m
|
||||||
});
|
});
|
||||||
|
entity.UpdateTotals();
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Coins(4.2m - 0.5m + 0.01m / 2), accounting.Due);
|
Assert.Equal(4.2m - 0.5m + 0.01m / 2, accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||||
Assert.Equal(Money.Coins(1.5m), accounting.Paid);
|
Assert.Equal(1.5m, accounting.Paid);
|
||||||
Assert.Equal(Money.Coins(5.2m + 0.01m / 2), accounting.TotalDue); // The fee for LTC added
|
Assert.Equal(5.2m + 0.01m / 2, accounting.TotalDue); // The fee for LTC added
|
||||||
Assert.Equal(2, accounting.TxRequired);
|
Assert.Equal(2, accounting.TxRequired);
|
||||||
|
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Coins(8.21m - 1.0m + 0.01m), accounting.Due);
|
Assert.Equal(8.21m - 1.0m + 0.01m, accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||||
Assert.Equal(Money.Coins(3.0m), accounting.Paid);
|
Assert.Equal(3.0m, accounting.Paid);
|
||||||
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 + 0.01m), accounting.TotalDue);
|
Assert.Equal(10.01m + 0.1m * 2 + 0.01m, accounting.TotalDue);
|
||||||
Assert.Equal(2, accounting.TxRequired);
|
Assert.Equal(2, accounting.TxRequired);
|
||||||
|
|
||||||
var remaining = Money.Coins(4.2m - 0.5m + 0.01m / 2);
|
var remaining = Money.Coins(4.2m - 0.5m + 0.01m / 2.0m).ToDecimal(MoneyUnit.BTC);
|
||||||
entity.Payments.Add(new PaymentEntity()
|
entity.Payments.Add(new PaymentEntity()
|
||||||
{
|
{
|
||||||
CryptoCode = "BTC",
|
Currency = "BTC",
|
||||||
Output = new TxOut(remaining, new Key()),
|
Output = new TxOut(Money.Coins(remaining), new Key()),
|
||||||
Accounted = true,
|
Accounted = true,
|
||||||
NetworkFee = 0.1m
|
NetworkFee = 0.1m
|
||||||
});
|
});
|
||||||
|
entity.UpdateTotals();
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Zero, accounting.Due);
|
Assert.Equal(0.0m, accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.0m) + remaining, accounting.CryptoPaid);
|
Assert.Equal(1.0m + remaining, accounting.CryptoPaid);
|
||||||
Assert.Equal(Money.Coins(1.5m) + remaining, accounting.Paid);
|
Assert.Equal(1.5m + remaining, accounting.Paid);
|
||||||
Assert.Equal(Money.Coins(5.2m + 0.01m / 2), accounting.TotalDue);
|
Assert.Equal(5.2m + 0.01m / 2, accounting.TotalDue);
|
||||||
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
||||||
Assert.Equal(2, accounting.TxRequired);
|
Assert.Equal(2, accounting.TxRequired);
|
||||||
|
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Zero, accounting.Due);
|
Assert.Equal(0.0m, accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.0m), accounting.CryptoPaid);
|
Assert.Equal(1.0m, accounting.CryptoPaid);
|
||||||
Assert.Equal(Money.Coins(3.0m) + remaining * 2, accounting.Paid);
|
Assert.Equal(3.0m + remaining * 2, accounting.Paid);
|
||||||
// Paying 2 BTC fee, LTC fee removed because fully paid
|
// Paying 2 BTC fee, LTC fee removed because fully paid
|
||||||
Assert.Equal(Money.Coins(10.01m + 0.1m * 2 + 0.1m * 2 /* + 0.01m no need to pay this fee anymore */),
|
Assert.Equal(10.01m + 0.1m * 2 + 0.1m * 2 /* + 0.01m no need to pay this fee anymore */,
|
||||||
accounting.TotalDue);
|
accounting.TotalDue);
|
||||||
Assert.Equal(1, accounting.TxRequired);
|
Assert.Equal(1, accounting.TxRequired);
|
||||||
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
Assert.Equal(accounting.Paid, accounting.TotalDue);
|
||||||
|
@ -548,27 +596,29 @@ namespace BTCPayServer.Tests
|
||||||
entity.Payments = new List<PaymentEntity>();
|
entity.Payments = new List<PaymentEntity>();
|
||||||
entity.SetPaymentMethod(new PaymentMethod()
|
entity.SetPaymentMethod(new PaymentMethod()
|
||||||
{
|
{
|
||||||
CryptoCode = "BTC",
|
Currency = "BTC",
|
||||||
Rate = 5000,
|
Rate = 5000,
|
||||||
NextNetworkFee = Money.Coins(0.1m)
|
NextNetworkFee = Money.Coins(0.1m)
|
||||||
});
|
});
|
||||||
entity.Price = 5000;
|
entity.Price = 5000;
|
||||||
entity.PaymentTolerance = 0;
|
entity.PaymentTolerance = 0;
|
||||||
|
entity.UpdateTotals();
|
||||||
|
|
||||||
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
var paymentMethod = entity.GetPaymentMethods().TryGet("BTC", PaymentTypes.BTCLike);
|
||||||
var accounting = paymentMethod.Calculate();
|
var accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Coins(1.1m), accounting.Due);
|
Assert.Equal(1.1m, accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
|
Assert.Equal(1.1m, accounting.TotalDue);
|
||||||
Assert.Equal(Money.Coins(1.1m), accounting.MinimumTotalDue);
|
Assert.Equal(1.1m, accounting.MinimumTotalDue);
|
||||||
|
|
||||||
entity.PaymentTolerance = 10;
|
entity.PaymentTolerance = 10;
|
||||||
|
entity.UpdateTotals();
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Coins(0.99m), accounting.MinimumTotalDue);
|
Assert.Equal(0.99m, accounting.MinimumTotalDue);
|
||||||
|
|
||||||
entity.PaymentTolerance = 100;
|
entity.PaymentTolerance = 100;
|
||||||
|
entity.UpdateTotals();
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Satoshis(1), accounting.MinimumTotalDue);
|
Assert.Equal(0.0000_0001m, accounting.MinimumTotalDue);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -1884,11 +1934,6 @@ namespace BTCPayServer.Tests
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
|
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
|
||||||
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
|
||||||
{
|
|
||||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null, null),
|
|
||||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
|
|
||||||
});
|
|
||||||
var networkBTC = networkProvider.GetNetwork("BTC");
|
var networkBTC = networkProvider.GetNetwork("BTC");
|
||||||
var networkLTC = networkProvider.GetNetwork("LTC");
|
var networkLTC = networkProvider.GetNetwork("LTC");
|
||||||
InvoiceEntity invoiceEntity = new InvoiceEntity();
|
InvoiceEntity invoiceEntity = new InvoiceEntity();
|
||||||
|
@ -1896,14 +1941,14 @@ namespace BTCPayServer.Tests
|
||||||
invoiceEntity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
invoiceEntity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||||
invoiceEntity.Price = 100;
|
invoiceEntity.Price = 100;
|
||||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||||
paymentMethods.Add(new PaymentMethod() { Network = networkBTC, CryptoCode = "BTC", Rate = 10513.44m, }
|
paymentMethods.Add(new PaymentMethod() { Network = networkBTC, Currency = "BTC", Rate = 10513.44m, }
|
||||||
.SetPaymentMethodDetails(
|
.SetPaymentMethodDetails(
|
||||||
new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
||||||
{
|
{
|
||||||
NextNetworkFee = Money.Coins(0.00000100m),
|
NextNetworkFee = Money.Coins(0.00000100m),
|
||||||
DepositAddress = dummy
|
DepositAddress = dummy
|
||||||
}));
|
}));
|
||||||
paymentMethods.Add(new PaymentMethod() { Network = networkLTC, CryptoCode = "LTC", Rate = 216.79m }
|
paymentMethods.Add(new PaymentMethod() { Network = networkLTC, Currency = "LTC", Rate = 216.79m }
|
||||||
.SetPaymentMethodDetails(
|
.SetPaymentMethodDetails(
|
||||||
new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
||||||
{
|
{
|
||||||
|
@ -1919,7 +1964,7 @@ namespace BTCPayServer.Tests
|
||||||
new PaymentEntity()
|
new PaymentEntity()
|
||||||
{
|
{
|
||||||
Accounted = true,
|
Accounted = true,
|
||||||
CryptoCode = "BTC",
|
Currency = "BTC",
|
||||||
NetworkFee = 0.00000100m,
|
NetworkFee = 0.00000100m,
|
||||||
Network = networkProvider.GetNetwork("BTC"),
|
Network = networkProvider.GetNetwork("BTC"),
|
||||||
}
|
}
|
||||||
|
@ -1928,34 +1973,33 @@ namespace BTCPayServer.Tests
|
||||||
Network = networkProvider.GetNetwork("BTC"),
|
Network = networkProvider.GetNetwork("BTC"),
|
||||||
Output = new TxOut() { Value = Money.Coins(0.00151263m) }
|
Output = new TxOut() { Value = Money.Coins(0.00151263m) }
|
||||||
}));
|
}));
|
||||||
|
invoiceEntity.UpdateTotals();
|
||||||
accounting = btc.Calculate();
|
accounting = btc.Calculate();
|
||||||
invoiceEntity.Payments.Add(
|
invoiceEntity.Payments.Add(
|
||||||
new PaymentEntity()
|
new PaymentEntity()
|
||||||
{
|
{
|
||||||
Accounted = true,
|
Accounted = true,
|
||||||
CryptoCode = "BTC",
|
Currency = "BTC",
|
||||||
NetworkFee = 0.00000100m,
|
NetworkFee = 0.00000100m,
|
||||||
Network = networkProvider.GetNetwork("BTC")
|
Network = networkProvider.GetNetwork("BTC")
|
||||||
}
|
}
|
||||||
.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
||||||
{
|
{
|
||||||
Network = networkProvider.GetNetwork("BTC"),
|
Network = networkProvider.GetNetwork("BTC"),
|
||||||
Output = new TxOut() { Value = accounting.Due }
|
Output = new TxOut() { Value = Money.Coins(accounting.Due) }
|
||||||
}));
|
}));
|
||||||
|
invoiceEntity.UpdateTotals();
|
||||||
accounting = btc.Calculate();
|
accounting = btc.Calculate();
|
||||||
Assert.Equal(Money.Zero, accounting.Due);
|
Assert.Equal(0.0m, accounting.Due);
|
||||||
Assert.Equal(Money.Zero, accounting.DueUncapped);
|
Assert.Equal(0.0m, accounting.DueUncapped);
|
||||||
|
|
||||||
var ltc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
var ltc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike));
|
||||||
accounting = ltc.Calculate();
|
accounting = ltc.Calculate();
|
||||||
|
|
||||||
Assert.Equal(Money.Zero, accounting.Due);
|
Assert.Equal(0.0m, accounting.Due);
|
||||||
// LTC might have over paid due to BTC paying above what it should (round 1 satoshi up)
|
// LTC might should be over paid due to BTC paying above what it should (round 1 satoshi up), but we handle this case
|
||||||
Assert.True(accounting.DueUncapped < Money.Zero);
|
// and set DueUncapped to zero.
|
||||||
|
Assert.Equal(0.0m, accounting.DueUncapped);
|
||||||
var paymentMethod = InvoiceWatcher.GetNearestClearedPayment(paymentMethods, out var accounting2);
|
|
||||||
Assert.Equal(btc.CryptoCode, paymentMethod.CryptoCode);
|
|
||||||
#pragma warning restore CS0618
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
@ -1761,7 +1761,7 @@ namespace BTCPayServer.Tests
|
||||||
var parsedJson = await GetExport(user);
|
var parsedJson = await GetExport(user);
|
||||||
Assert.Equal(3, parsedJson.Length);
|
Assert.Equal(3, parsedJson.Length);
|
||||||
|
|
||||||
var invoiceDueAfterFirstPayment = (3 * networkFee).ToDecimal(MoneyUnit.BTC) * invoice.Rate;
|
var invoiceDueAfterFirstPayment = 3 * networkFee.ToDecimal(MoneyUnit.BTC) * invoice.Rate;
|
||||||
var pay1str = parsedJson[0].ToString();
|
var pay1str = parsedJson[0].ToString();
|
||||||
Assert.Contains("\"InvoiceItemDesc\": \"Some \\\", description\"", pay1str);
|
Assert.Contains("\"InvoiceItemDesc\": \"Some \\\", description\"", pay1str);
|
||||||
Assert.Equal(invoiceDueAfterFirstPayment, GetFieldValue(pay1str, "InvoiceDue"));
|
Assert.Equal(invoiceDueAfterFirstPayment, GetFieldValue(pay1str, "InvoiceDue"));
|
||||||
|
|
|
@ -396,7 +396,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
return this.CreateValidationError(ModelState);
|
return this.CreateValidationError(ModelState);
|
||||||
|
|
||||||
var accounting = invoicePaymentMethod.Calculate();
|
var accounting = invoicePaymentMethod.Calculate();
|
||||||
var cryptoPaid = accounting.Paid.ToDecimal(MoneyUnit.BTC);
|
var cryptoPaid = accounting.Paid;
|
||||||
var cdCurrency = _currencyNameTable.GetCurrencyData(invoice.Currency, true);
|
var cdCurrency = _currencyNameTable.GetCurrencyData(invoice.Currency, true);
|
||||||
var paidCurrency = Math.Round(cryptoPaid * invoicePaymentMethod.Rate, cdCurrency.Divisibility);
|
var paidCurrency = Math.Round(cryptoPaid * invoicePaymentMethod.Rate, cdCurrency.Divisibility);
|
||||||
var rateResult = await _rateProvider.FetchRate(
|
var rateResult = await _rateProvider.FetchRate(
|
||||||
|
@ -464,7 +464,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
return this.CreateValidationError(ModelState);
|
return this.CreateValidationError(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
var dueAmount = accounting.TotalDue.ToDecimal(MoneyUnit.BTC);
|
var dueAmount = accounting.TotalDue;
|
||||||
createPullPayment.Currency = cryptoCode;
|
createPullPayment.Currency = cryptoCode;
|
||||||
createPullPayment.Amount = Math.Round(paidAmount - dueAmount, appliedDivisibility);
|
createPullPayment.Amount = Math.Round(paidAmount - dueAmount, appliedDivisibility);
|
||||||
createPullPayment.AutoApproveClaims = true;
|
createPullPayment.AutoApproveClaims = true;
|
||||||
|
@ -580,11 +580,11 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||||
CryptoCode = method.GetId().CryptoCode,
|
CryptoCode = method.GetId().CryptoCode,
|
||||||
Destination = details.GetPaymentDestination(),
|
Destination = details.GetPaymentDestination(),
|
||||||
Rate = method.Rate,
|
Rate = method.Rate,
|
||||||
Due = accounting.DueUncapped.ToDecimal(MoneyUnit.BTC),
|
Due = accounting.DueUncapped,
|
||||||
TotalPaid = accounting.Paid.ToDecimal(MoneyUnit.BTC),
|
TotalPaid = accounting.Paid,
|
||||||
PaymentMethodPaid = accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC),
|
PaymentMethodPaid = accounting.CryptoPaid,
|
||||||
Amount = accounting.TotalDue.ToDecimal(MoneyUnit.BTC),
|
Amount = accounting.TotalDue,
|
||||||
NetworkFee = accounting.NetworkFee.ToDecimal(MoneyUnit.BTC),
|
NetworkFee = accounting.NetworkFee,
|
||||||
PaymentLink =
|
PaymentLink =
|
||||||
method.GetId().PaymentType.GetPaymentLink(method.Network, entity, details, accounting.Due,
|
method.GetId().PaymentType.GetPaymentLink(method.Network, entity, details, accounting.Due,
|
||||||
Request.GetAbsoluteRoot()),
|
Request.GetAbsoluteRoot()),
|
||||||
|
|
|
@ -55,7 +55,7 @@ namespace BTCPayServer.Controllers
|
||||||
return Ok(new
|
return Ok(new
|
||||||
{
|
{
|
||||||
Txid = txid,
|
Txid = txid,
|
||||||
AmountRemaining = (paymentMethod.Calculate().Due - amount).ToUnit(MoneyUnit.BTC),
|
AmountRemaining = paymentMethod.Calculate().Due - amount.ToDecimal(MoneyUnit.BTC),
|
||||||
SuccessMessage = $"Created transaction {txid}"
|
SuccessMessage = $"Created transaction {txid}"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -70,11 +70,11 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
var bolt11 = BOLT11PaymentRequest.Parse(destination, network);
|
var bolt11 = BOLT11PaymentRequest.Parse(destination, network);
|
||||||
var paymentHash = bolt11.PaymentHash?.ToString();
|
var paymentHash = bolt11.PaymentHash?.ToString();
|
||||||
var paid = new Money(response.Details.TotalAmount.ToUnit(LightMoneyUnit.Satoshi), MoneyUnit.Satoshi);
|
var paid = response.Details.TotalAmount.ToDecimal(LightMoneyUnit.BTC);
|
||||||
return Ok(new
|
return Ok(new
|
||||||
{
|
{
|
||||||
Txid = paymentHash,
|
Txid = paymentHash,
|
||||||
AmountRemaining = (paymentMethod.Calculate().TotalDue - paid).ToUnit(MoneyUnit.BTC),
|
AmountRemaining = paymentMethod.Calculate().TotalDue - paid,
|
||||||
SuccessMessage = $"Sent payment {paymentHash}"
|
SuccessMessage = $"Sent payment {paymentHash}"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,18 +228,14 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
string txId = paymentData.GetPaymentId();
|
string txId = paymentData.GetPaymentId();
|
||||||
string? link = GetTransactionLink(paymentMethodId, txId);
|
string? link = GetTransactionLink(paymentMethodId, txId);
|
||||||
var paymentMethod = i.GetPaymentMethod(paymentMethodId);
|
|
||||||
var amount = paymentData.GetValue();
|
|
||||||
var rate = paymentMethod.Rate;
|
|
||||||
var paid = (amount - paymentEntity.NetworkFee) * rate;
|
|
||||||
|
|
||||||
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment
|
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment
|
||||||
{
|
{
|
||||||
Amount = amount,
|
Amount = paymentEntity.PaidAmount.Gross,
|
||||||
Paid = paid,
|
Paid = paymentEntity.PaidAmount.Net,
|
||||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
|
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
|
||||||
PaidFormatted = _displayFormatter.Currency(paid, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
PaidFormatted = _displayFormatter.Currency(paymentEntity.PaidAmount.Net, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||||
RateFormatted = _displayFormatter.Currency(rate, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||||
Link = link,
|
Link = link,
|
||||||
Id = txId,
|
Id = txId,
|
||||||
|
@ -364,8 +360,8 @@ namespace BTCPayServer.Controllers
|
||||||
if (paymentMethod != null)
|
if (paymentMethod != null)
|
||||||
{
|
{
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
cryptoPaid = accounting.Paid.ToDecimal(MoneyUnit.BTC);
|
cryptoPaid = accounting.Paid;
|
||||||
dueAmount = accounting.TotalDue.ToDecimal(MoneyUnit.BTC);
|
dueAmount = accounting.TotalDue;
|
||||||
paidAmount = cryptoPaid.RoundToSignificant(appliedDivisibility);
|
paidAmount = cryptoPaid.RoundToSignificant(appliedDivisibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -560,7 +556,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
var accounting = data.Calculate();
|
var accounting = data.Calculate();
|
||||||
var paymentMethodId = data.GetId();
|
var paymentMethodId = data.GetId();
|
||||||
var overpaidAmount = accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC);
|
var overpaidAmount = accounting.OverpaidHelper;
|
||||||
|
|
||||||
if (overpaidAmount > 0)
|
if (overpaidAmount > 0)
|
||||||
{
|
{
|
||||||
|
@ -571,8 +567,8 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
PaymentMethodId = paymentMethodId,
|
PaymentMethodId = paymentMethodId,
|
||||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||||
Due = _displayFormatter.Currency(accounting.Due.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode),
|
Due = _displayFormatter.Currency(accounting.Due, paymentMethodId.CryptoCode),
|
||||||
Paid = _displayFormatter.Currency(accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode),
|
Paid = _displayFormatter.Currency(accounting.CryptoPaid, paymentMethodId.CryptoCode),
|
||||||
Overpaid = _displayFormatter.Currency(overpaidAmount, paymentMethodId.CryptoCode),
|
Overpaid = _displayFormatter.Currency(overpaidAmount, paymentMethodId.CryptoCode),
|
||||||
Address = data.GetPaymentMethodDetails().GetPaymentDestination(),
|
Address = data.GetPaymentMethodDetails().GetPaymentDestination(),
|
||||||
Rate = ExchangeRate(data.GetId().CryptoCode, data),
|
Rate = ExchangeRate(data.GetId().CryptoCode, data),
|
||||||
|
@ -827,7 +823,6 @@ namespace BTCPayServer.Controllers
|
||||||
var dto = invoice.EntityToDTO();
|
var dto = invoice.EntityToDTO();
|
||||||
var accounting = paymentMethod.Calculate();
|
var accounting = paymentMethod.Calculate();
|
||||||
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
|
var paymentMethodHandler = _paymentMethodHandlerDictionary[paymentMethodId];
|
||||||
var divisibility = _CurrencyNameTable.GetNumberFormatInfo(paymentMethod.GetId().CryptoCode, false)?.CurrencyDecimalDigits;
|
|
||||||
|
|
||||||
switch (lang?.ToLowerInvariant())
|
switch (lang?.ToLowerInvariant())
|
||||||
{
|
{
|
||||||
|
@ -885,10 +880,10 @@ namespace BTCPayServer.Controllers
|
||||||
OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback,
|
OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback,
|
||||||
CryptoImage = Request.GetRelativePathOrAbsolute(paymentMethodHandler.GetCryptoImage(paymentMethodId)),
|
CryptoImage = Request.GetRelativePathOrAbsolute(paymentMethodHandler.GetCryptoImage(paymentMethodId)),
|
||||||
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
|
BtcAddress = paymentMethodDetails.GetPaymentDestination(),
|
||||||
BtcDue = accounting.Due.ShowMoney(divisibility),
|
BtcDue = accounting.ShowMoney(accounting.Due),
|
||||||
BtcPaid = accounting.Paid.ShowMoney(divisibility),
|
BtcPaid = accounting.ShowMoney(accounting.Paid),
|
||||||
InvoiceCurrency = invoice.Currency,
|
InvoiceCurrency = invoice.Currency,
|
||||||
OrderAmount = (accounting.TotalDue - accounting.NetworkFee).ShowMoney(divisibility),
|
OrderAmount = accounting.ShowMoney(accounting.TotalDue - accounting.NetworkFee),
|
||||||
IsUnsetTopUp = invoice.IsUnsetTopUp(),
|
IsUnsetTopUp = invoice.IsUnsetTopUp(),
|
||||||
CustomerEmail = invoice.RefundMail,
|
CustomerEmail = invoice.RefundMail,
|
||||||
RequiresRefundEmail = invoice.RequiresRefundEmail ?? storeBlob.RequiresRefundEmail,
|
RequiresRefundEmail = invoice.RequiresRefundEmail ?? storeBlob.RequiresRefundEmail,
|
||||||
|
|
|
@ -121,7 +121,7 @@ namespace BTCPayServer.Controllers
|
||||||
internal async Task<InvoiceEntity> CreateInvoiceCoreRaw(BitpayCreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string>? additionalTags = null, CancellationToken cancellationToken = default, Action<InvoiceEntity>? entityManipulator = null)
|
internal async Task<InvoiceEntity> CreateInvoiceCoreRaw(BitpayCreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string>? additionalTags = null, CancellationToken cancellationToken = default, Action<InvoiceEntity>? entityManipulator = null)
|
||||||
{
|
{
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
var entity = _InvoiceRepository.CreateNewInvoice();
|
var entity = _InvoiceRepository.CreateNewInvoice(store.Id);
|
||||||
entity.ExpirationTime = invoice.ExpirationTime is DateTimeOffset v ? v : entity.InvoiceTime + storeBlob.InvoiceExpiration;
|
entity.ExpirationTime = invoice.ExpirationTime is DateTimeOffset v ? v : entity.InvoiceTime + storeBlob.InvoiceExpiration;
|
||||||
entity.MonitoringExpiration = entity.ExpirationTime + storeBlob.MonitoringExpiration;
|
entity.MonitoringExpiration = entity.ExpirationTime + storeBlob.MonitoringExpiration;
|
||||||
if (entity.ExpirationTime - TimeSpan.FromSeconds(30.0) < entity.InvoiceTime)
|
if (entity.ExpirationTime - TimeSpan.FromSeconds(30.0) < entity.InvoiceTime)
|
||||||
|
@ -237,7 +237,7 @@ namespace BTCPayServer.Controllers
|
||||||
public async Task<InvoiceEntity> CreateInvoiceCoreRaw(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string>? additionalTags = null, CancellationToken cancellationToken = default, Action<InvoiceEntity>? entityManipulator = null)
|
public async Task<InvoiceEntity> CreateInvoiceCoreRaw(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string>? additionalTags = null, CancellationToken cancellationToken = default, Action<InvoiceEntity>? entityManipulator = null)
|
||||||
{
|
{
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
var entity = _InvoiceRepository.CreateNewInvoice();
|
var entity = _InvoiceRepository.CreateNewInvoice(store.Id);
|
||||||
entity.ServerUrl = serverUrl;
|
entity.ServerUrl = serverUrl;
|
||||||
entity.ExpirationTime = entity.InvoiceTime + (invoice.Checkout.Expiration ?? storeBlob.InvoiceExpiration);
|
entity.ExpirationTime = entity.InvoiceTime + (invoice.Checkout.Expiration ?? storeBlob.InvoiceExpiration);
|
||||||
entity.MonitoringExpiration = entity.ExpirationTime + (invoice.Checkout.Monitoring ?? storeBlob.MonitoringExpiration);
|
entity.MonitoringExpiration = entity.ExpirationTime + (invoice.Checkout.Monitoring ?? storeBlob.MonitoringExpiration);
|
||||||
|
@ -314,6 +314,7 @@ namespace BTCPayServer.Controllers
|
||||||
entity.RefundMail = entity.Metadata.BuyerEmail;
|
entity.RefundMail = entity.Metadata.BuyerEmail;
|
||||||
}
|
}
|
||||||
entity.Status = InvoiceStatusLegacy.New;
|
entity.Status = InvoiceStatusLegacy.New;
|
||||||
|
entity.UpdateTotals();
|
||||||
HashSet<CurrencyPair> currencyPairsToFetch = new HashSet<CurrencyPair>();
|
HashSet<CurrencyPair> currencyPairsToFetch = new HashSet<CurrencyPair>();
|
||||||
var rules = storeBlob.GetRateRules(_NetworkProvider);
|
var rules = storeBlob.GetRateRules(_NetworkProvider);
|
||||||
var excludeFilter = storeBlob.GetExcludedPaymentMethods(); // Here we can compose filters from other origin with PaymentFilter.Any()
|
var excludeFilter = storeBlob.GetExcludedPaymentMethods(); // Here we can compose filters from other origin with PaymentFilter.Any()
|
||||||
|
@ -402,7 +403,7 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
using (logs.Measure("Saving invoice"))
|
using (logs.Measure("Saving invoice"))
|
||||||
{
|
{
|
||||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, additionalSearchTerms);
|
await _InvoiceRepository.CreateInvoiceAsync(entity, additionalSearchTerms);
|
||||||
foreach (var method in paymentMethods)
|
foreach (var method in paymentMethods)
|
||||||
{
|
{
|
||||||
if (method.GetPaymentMethodDetails() is BitcoinLikeOnChainPaymentMethod bp)
|
if (method.GetPaymentMethodDetails() is BitcoinLikeOnChainPaymentMethod bp)
|
||||||
|
@ -506,7 +507,7 @@ namespace BTCPayServer.Controllers
|
||||||
await fetchingByCurrencyPair[new CurrencyPair(supportedPaymentMethod.PaymentId.CryptoCode, criteria.Value.Currency)];
|
await fetchingByCurrencyPair[new CurrencyPair(supportedPaymentMethod.PaymentId.CryptoCode, criteria.Value.Currency)];
|
||||||
if (currentRateToCrypto?.BidAsk != null)
|
if (currentRateToCrypto?.BidAsk != null)
|
||||||
{
|
{
|
||||||
var amount = paymentMethod.Calculate().Due.GetValue(network as BTCPayNetwork);
|
var amount = paymentMethod.Calculate().Due;
|
||||||
var limitValueCrypto = criteria.Value.Value / currentRateToCrypto.BidAsk.Bid;
|
var limitValueCrypto = criteria.Value.Value / currentRateToCrypto.BidAsk.Bid;
|
||||||
|
|
||||||
if (amount < limitValueCrypto && criteria.Above)
|
if (amount < limitValueCrypto && criteria.Above)
|
||||||
|
|
|
@ -548,7 +548,7 @@ namespace BTCPayServer
|
||||||
lnurlRequest.Metadata = JsonConvert.SerializeObject(lnUrlMetadata.Select(kv => new[] { kv.Key, kv.Value }));
|
lnurlRequest.Metadata = JsonConvert.SerializeObject(lnUrlMetadata.Select(kv => new[] { kv.Key, kv.Value }));
|
||||||
if (i.Type != InvoiceType.TopUp)
|
if (i.Type != InvoiceType.TopUp)
|
||||||
{
|
{
|
||||||
lnurlRequest.MinSendable = new LightMoney(pm.Calculate().Due.ToDecimal(MoneyUnit.Satoshi), LightMoneyUnit.Satoshi);
|
lnurlRequest.MinSendable = LightMoney.Coins(pm.Calculate().Due);
|
||||||
if (!allowOverpay)
|
if (!allowOverpay)
|
||||||
lnurlRequest.MaxSendable = lnurlRequest.MinSendable;
|
lnurlRequest.MaxSendable = lnurlRequest.MinSendable;
|
||||||
}
|
}
|
||||||
|
|
|
@ -303,7 +303,7 @@ public class BitcoinLikePayoutHandler : IPayoutHandler
|
||||||
bip21.Add(newUri.Uri.ToString());
|
bip21.Add(newUri.Uri.ToString());
|
||||||
break;
|
break;
|
||||||
case AddressClaimDestination addressClaimDestination:
|
case AddressClaimDestination addressClaimDestination:
|
||||||
var bip21New = network.GenerateBIP21(addressClaimDestination.Address.ToString(), new Money(blob.CryptoAmount.Value, MoneyUnit.BTC));
|
var bip21New = network.GenerateBIP21(addressClaimDestination.Address.ToString(), blob.CryptoAmount.Value);
|
||||||
bip21New.QueryParams.Add("payout", payout.Id);
|
bip21New.QueryParams.Add("payout", payout.Id);
|
||||||
bip21.Add(bip21New.ToString());
|
bip21.Add(bip21New.ToString());
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -30,6 +30,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.Payment;
|
using NBitcoin.Payment;
|
||||||
|
using NBitpayClient;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using InvoiceCryptoInfo = BTCPayServer.Services.Invoices.InvoiceCryptoInfo;
|
using InvoiceCryptoInfo = BTCPayServer.Services.Invoices.InvoiceCryptoInfo;
|
||||||
|
@ -38,6 +39,15 @@ namespace BTCPayServer
|
||||||
{
|
{
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
|
public static DateTimeOffset TruncateMilliSeconds(this DateTimeOffset dt) => new (dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, 0, dt.Offset);
|
||||||
|
public static decimal? GetDue(this InvoiceCryptoInfo invoiceCryptoInfo)
|
||||||
|
{
|
||||||
|
if (invoiceCryptoInfo is null)
|
||||||
|
return null;
|
||||||
|
if (decimal.TryParse(invoiceCryptoInfo.Due, NumberStyles.Any, CultureInfo.InvariantCulture, out var v))
|
||||||
|
return v;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
public static Task<BufferizedFormFile> Bufferize(this IFormFile formFile)
|
public static Task<BufferizedFormFile> Bufferize(this IFormFile formFile)
|
||||||
{
|
{
|
||||||
return BufferizedFormFile.Bufferize(formFile);
|
return BufferizedFormFile.Bufferize(formFile);
|
||||||
|
@ -382,20 +392,6 @@ namespace BTCPayServer
|
||||||
return controller.View("PostRedirect", redirectVm);
|
return controller.View("PostRedirect", redirectVm);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
|
|
||||||
{
|
|
||||||
var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
|
|
||||||
var relationalCommandCache = enumerator.Private("_relationalCommandCache");
|
|
||||||
var selectExpression = relationalCommandCache.Private<Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression>("_selectExpression");
|
|
||||||
var factory = relationalCommandCache.Private<Microsoft.EntityFrameworkCore.Query.IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");
|
|
||||||
|
|
||||||
var sqlGenerator = factory.Create();
|
|
||||||
var command = sqlGenerator.GetCommand(selectExpression);
|
|
||||||
|
|
||||||
string sql = command.CommandText;
|
|
||||||
return sql;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BTCPayNetworkProvider ConfigureNetworkProvider(this IConfiguration configuration, Logs logs)
|
public static BTCPayNetworkProvider ConfigureNetworkProvider(this IConfiguration configuration, Logs logs)
|
||||||
{
|
{
|
||||||
var _networkType = DefaultConfiguration.GetNetworkType(configuration);
|
var _networkType = DefaultConfiguration.GetNetworkType(configuration);
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Logging;
|
using BTCPayServer.Logging;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Notifications;
|
using BTCPayServer.Services.Notifications;
|
||||||
using BTCPayServer.Services.Notifications.Blobs;
|
using BTCPayServer.Services.Notifications.Blobs;
|
||||||
|
@ -83,31 +84,13 @@ namespace BTCPayServer.HostedServices
|
||||||
if (invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
if (invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.ExpiredPaidPartial) { PaidPartial = paidPartial });
|
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.ExpiredPaidPartial) { PaidPartial = paidPartial });
|
||||||
}
|
}
|
||||||
var allPaymentMethods = invoice.GetPaymentMethods();
|
|
||||||
var paymentMethod = GetNearestClearedPayment(allPaymentMethods, out var accounting);
|
var hasPayment = invoice.GetPayments(true).Any();
|
||||||
if (allPaymentMethods.Any() && paymentMethod == null)
|
|
||||||
return;
|
|
||||||
if (accounting is null && invoice.Price is 0m)
|
|
||||||
{
|
|
||||||
accounting = new PaymentMethodAccounting()
|
|
||||||
{
|
|
||||||
Due = Money.Zero,
|
|
||||||
Paid = Money.Zero,
|
|
||||||
CryptoPaid = Money.Zero,
|
|
||||||
DueUncapped = Money.Zero,
|
|
||||||
NetworkFee = Money.Zero,
|
|
||||||
TotalDue = Money.Zero,
|
|
||||||
TxCount = 0,
|
|
||||||
TxRequired = 0,
|
|
||||||
MinimumTotalDue = Money.Zero,
|
|
||||||
NetworkFeeAlreadyPaid = Money.Zero
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (invoice.Status == InvoiceStatusLegacy.New || invoice.Status == InvoiceStatusLegacy.Expired)
|
if (invoice.Status == InvoiceStatusLegacy.New || invoice.Status == InvoiceStatusLegacy.Expired)
|
||||||
{
|
{
|
||||||
var isPaid = invoice.IsUnsetTopUp() ?
|
var isPaid = invoice.IsUnsetTopUp() ?
|
||||||
accounting.Paid > Money.Zero :
|
hasPayment :
|
||||||
accounting.Paid >= accounting.MinimumTotalDue;
|
!invoice.IsUnderPaid;
|
||||||
if (isPaid)
|
if (isPaid)
|
||||||
{
|
{
|
||||||
if (invoice.Status == InvoiceStatusLegacy.New)
|
if (invoice.Status == InvoiceStatusLegacy.New)
|
||||||
|
@ -117,13 +100,15 @@ namespace BTCPayServer.HostedServices
|
||||||
if (invoice.IsUnsetTopUp())
|
if (invoice.IsUnsetTopUp())
|
||||||
{
|
{
|
||||||
invoice.ExceptionStatus = InvoiceExceptionStatus.None;
|
invoice.ExceptionStatus = InvoiceExceptionStatus.None;
|
||||||
invoice.Price = (accounting.Paid - accounting.NetworkFeeAlreadyPaid).ToDecimal(MoneyUnit.BTC) * paymentMethod.Rate;
|
// We know there is at least one payment because hasPayment is true
|
||||||
accounting = paymentMethod.Calculate();
|
var payment = invoice.GetPayments(true).First();
|
||||||
|
invoice.Price = payment.InvoicePaidAmount.Net;
|
||||||
|
invoice.UpdateTotals();
|
||||||
context.BlobUpdated();
|
context.BlobUpdated();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? InvoiceExceptionStatus.PaidOver : InvoiceExceptionStatus.None;
|
invoice.ExceptionStatus = invoice.IsOverPaid ? InvoiceExceptionStatus.PaidOver : InvoiceExceptionStatus.None;
|
||||||
}
|
}
|
||||||
context.MarkDirty();
|
context.MarkDirty();
|
||||||
}
|
}
|
||||||
|
@ -135,7 +120,7 @@ namespace BTCPayServer.HostedServices
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments(true).Count != 0 && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidPartial)
|
if (hasPayment && invoice.IsUnderPaid && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidPartial)
|
||||||
{
|
{
|
||||||
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidPartial;
|
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidPartial;
|
||||||
context.MarkDirty();
|
context.MarkDirty();
|
||||||
|
@ -145,43 +130,43 @@ namespace BTCPayServer.HostedServices
|
||||||
// Just make sure RBF did not cancelled a payment
|
// Just make sure RBF did not cancelled a payment
|
||||||
if (invoice.Status == InvoiceStatusLegacy.Paid)
|
if (invoice.Status == InvoiceStatusLegacy.Paid)
|
||||||
{
|
{
|
||||||
if (accounting.MinimumTotalDue <= accounting.Paid && accounting.Paid <= accounting.TotalDue && invoice.ExceptionStatus == InvoiceExceptionStatus.PaidOver)
|
if (!invoice.IsUnderPaid && !invoice.IsOverPaid && invoice.ExceptionStatus == InvoiceExceptionStatus.PaidOver)
|
||||||
{
|
{
|
||||||
invoice.ExceptionStatus = InvoiceExceptionStatus.None;
|
invoice.ExceptionStatus = InvoiceExceptionStatus.None;
|
||||||
context.MarkDirty();
|
context.MarkDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accounting.Paid > accounting.TotalDue && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidOver)
|
if (invoice.IsOverPaid && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidOver)
|
||||||
{
|
{
|
||||||
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidOver;
|
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidOver;
|
||||||
context.MarkDirty();
|
context.MarkDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accounting.Paid < accounting.MinimumTotalDue)
|
if (invoice.IsUnderPaid)
|
||||||
{
|
{
|
||||||
invoice.Status = InvoiceStatusLegacy.New;
|
invoice.Status = InvoiceStatusLegacy.New;
|
||||||
invoice.ExceptionStatus = accounting.Paid == Money.Zero ? InvoiceExceptionStatus.None : InvoiceExceptionStatus.PaidPartial;
|
invoice.ExceptionStatus = hasPayment ? InvoiceExceptionStatus.PaidPartial : InvoiceExceptionStatus.None;
|
||||||
context.MarkDirty();
|
context.MarkDirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invoice.Status == InvoiceStatusLegacy.Paid)
|
if (invoice.Status == InvoiceStatusLegacy.Paid)
|
||||||
{
|
{
|
||||||
var confirmedAccounting =
|
var unconfPayments = invoice.GetPayments(true).Where(p => !p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy)).ToList();
|
||||||
paymentMethod?.Calculate(p => p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy)) ??
|
var unconfirmedPaid = unconfPayments.Select(p => p.InvoicePaidAmount.Net).Sum();
|
||||||
accounting;
|
var minimumDue = invoice.MinimumNetDue + unconfirmedPaid;
|
||||||
|
|
||||||
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
|
||||||
(confirmedAccounting.Paid < accounting.MinimumTotalDue))
|
(minimumDue > 0.0m))
|
||||||
{
|
{
|
||||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.FailedToConfirm));
|
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.FailedToConfirm));
|
||||||
invoice.Status = InvoiceStatusLegacy.Invalid;
|
invoice.Status = InvoiceStatusLegacy.Invalid;
|
||||||
context.MarkDirty();
|
context.MarkDirty();
|
||||||
}
|
}
|
||||||
else if (confirmedAccounting.Paid >= accounting.MinimumTotalDue)
|
else if (minimumDue <= 0.0m)
|
||||||
{
|
{
|
||||||
invoice.Status = InvoiceStatusLegacy.Confirmed;
|
invoice.Status = InvoiceStatusLegacy.Confirmed;
|
||||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Confirmed));
|
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Confirmed));
|
||||||
|
@ -191,9 +176,11 @@ namespace BTCPayServer.HostedServices
|
||||||
|
|
||||||
if (invoice.Status == InvoiceStatusLegacy.Confirmed)
|
if (invoice.Status == InvoiceStatusLegacy.Confirmed)
|
||||||
{
|
{
|
||||||
var completedAccounting = paymentMethod?.Calculate(p => p.GetCryptoPaymentData().PaymentCompleted(p)) ??
|
var unconfPayments = invoice.GetPayments(true).Where(p => !p.GetCryptoPaymentData().PaymentCompleted(p)).ToList();
|
||||||
accounting;
|
var unconfirmedPaid = unconfPayments.Select(p => p.InvoicePaidAmount.Net).Sum();
|
||||||
if (completedAccounting.Paid >= accounting.MinimumTotalDue)
|
var minimumDue = invoice.MinimumNetDue + unconfirmedPaid;
|
||||||
|
|
||||||
|
if (minimumDue <= 0.0m)
|
||||||
{
|
{
|
||||||
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Completed));
|
context.Events.Add(new InvoiceEvent(invoice, InvoiceEvent.Completed));
|
||||||
invoice.Status = InvoiceStatusLegacy.Complete;
|
invoice.Status = InvoiceStatusLegacy.Complete;
|
||||||
|
@ -203,25 +190,6 @@ namespace BTCPayServer.HostedServices
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PaymentMethod GetNearestClearedPayment(PaymentMethodDictionary allPaymentMethods, out PaymentMethodAccounting accounting)
|
|
||||||
{
|
|
||||||
PaymentMethod result = null;
|
|
||||||
accounting = null;
|
|
||||||
decimal nearestToZero = 0.0m;
|
|
||||||
foreach (var paymentMethod in allPaymentMethods)
|
|
||||||
{
|
|
||||||
var currentAccounting = paymentMethod.Calculate();
|
|
||||||
var distanceFromZero = Math.Abs(currentAccounting.DueUncapped.ToDecimal(MoneyUnit.BTC));
|
|
||||||
if (result == null || distanceFromZero < nearestToZero)
|
|
||||||
{
|
|
||||||
result = paymentMethod;
|
|
||||||
nearestToZero = distanceFromZero;
|
|
||||||
accounting = currentAccounting;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Watch(string invoiceId)
|
private void Watch(string invoiceId)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(invoiceId);
|
ArgumentNullException.ThrowIfNull(invoiceId);
|
||||||
|
@ -380,7 +348,7 @@ namespace BTCPayServer.HostedServices
|
||||||
if ((onChainPaymentData.ConfirmationCount < network.MaxTrackedConfirmation && payment.Accounted)
|
if ((onChainPaymentData.ConfirmationCount < network.MaxTrackedConfirmation && payment.Accounted)
|
||||||
&& (onChainPaymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
|
&& (onChainPaymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
|
||||||
{
|
{
|
||||||
var client = _explorerClientProvider.GetExplorerClient(payment.GetCryptoCode());
|
var client = _explorerClientProvider.GetExplorerClient(payment.Currency);
|
||||||
var transactionResult = client is null ? null : await client.GetTransactionAsync(onChainPaymentData.Outpoint.Hash);
|
var transactionResult = client is null ? null : await client.GetTransactionAsync(onChainPaymentData.Outpoint.Hash);
|
||||||
var confirmationCount = transactionResult?.Confirmations ?? 0;
|
var confirmationCount = transactionResult?.Confirmations ?? 0;
|
||||||
onChainPaymentData.ConfirmationCount = confirmationCount;
|
onChainPaymentData.ConfirmationCount = confirmationCount;
|
||||||
|
|
|
@ -103,7 +103,7 @@ namespace BTCPayServer.HostedServices
|
||||||
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType == BitcoinPaymentType.Instance &&
|
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType == BitcoinPaymentType.Instance &&
|
||||||
invoiceEvent.Payment.GetCryptoPaymentData() is BitcoinLikePaymentData bitcoinLikePaymentData:
|
invoiceEvent.Payment.GetCryptoPaymentData() is BitcoinLikePaymentData bitcoinLikePaymentData:
|
||||||
{
|
{
|
||||||
var walletId = new WalletId(invoiceEvent.Invoice.StoreId, invoiceEvent.Payment.GetCryptoCode());
|
var walletId = new WalletId(invoiceEvent.Invoice.StoreId, invoiceEvent.Payment.Currency);
|
||||||
var transactionId = bitcoinLikePaymentData.Outpoint.Hash;
|
var transactionId = bitcoinLikePaymentData.Outpoint.Hash;
|
||||||
var labels = new List<Attachment>
|
var labels = new List<Attachment>
|
||||||
{
|
{
|
||||||
|
|
|
@ -167,7 +167,7 @@ namespace BTCPayServer.PaymentRequest
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
data.GetValue(),
|
data.GetValue(),
|
||||||
invoiceEvent.Payment.GetCryptoCode(),
|
invoiceEvent.Payment.Currency,
|
||||||
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType?.ToString()
|
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType?.ToString()
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,18 +123,14 @@ namespace BTCPayServer.PaymentRequest
|
||||||
|
|
||||||
string txId = paymentData.GetPaymentId();
|
string txId = paymentData.GetPaymentId();
|
||||||
string link = GetTransactionLink(paymentMethodId, txId);
|
string link = GetTransactionLink(paymentMethodId, txId);
|
||||||
var paymentMethod = entity.GetPaymentMethod(paymentMethodId);
|
|
||||||
var amount = paymentData.GetValue();
|
|
||||||
var rate = paymentMethod.Rate;
|
|
||||||
var paid = (amount - paymentEntity.NetworkFee) * rate;
|
|
||||||
|
|
||||||
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment
|
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment
|
||||||
{
|
{
|
||||||
Amount = amount,
|
Amount = paymentEntity.PaidAmount.Gross,
|
||||||
Paid = paid,
|
Paid = paymentEntity.InvoicePaidAmount.Net,
|
||||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
|
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
|
||||||
PaidFormatted = _displayFormatter.Currency(paid, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
PaidFormatted = _displayFormatter.Currency(paymentEntity.InvoicePaidAmount.Net, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||||
RateFormatted = _displayFormatter.Currency(rate, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||||
Link = link,
|
Link = link,
|
||||||
Id = txId,
|
Id = txId,
|
||||||
|
|
|
@ -211,7 +211,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||||
new Key().GetScriptPubKey(supportedPaymentMethod.AccountDerivation.ScriptPubKeyType());
|
new Key().GetScriptPubKey(supportedPaymentMethod.AccountDerivation.ScriptPubKeyType());
|
||||||
var dust = txOut.GetDustThreshold();
|
var dust = txOut.GetDustThreshold();
|
||||||
var amount = paymentMethod.Calculate().Due;
|
var amount = paymentMethod.Calculate().Due;
|
||||||
if (amount < dust)
|
if (amount < dust.ToDecimal(MoneyUnit.BTC))
|
||||||
throw new PaymentMethodUnavailableException("Amount below the dust threshold. For amounts of this size, it is recommended to enable an off-chain (Lightning) payment method");
|
throw new PaymentMethodUnavailableException("Amount below the dust threshold. For amounts of this size, it is recommended to enable an off-chain (Lightning) payment method");
|
||||||
}
|
}
|
||||||
if (preparePaymentObject is null)
|
if (preparePaymentObject is null)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
|
@ -28,7 +29,7 @@ namespace BTCPayServer.Payments
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails,
|
public override string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails,
|
||||||
Money cryptoInfoDue, string serverUri)
|
decimal cryptoInfoDue, string serverUri)
|
||||||
{
|
{
|
||||||
if (!paymentMethodDetails.Activated)
|
if (!paymentMethodDetails.Activated)
|
||||||
{
|
{
|
||||||
|
@ -74,7 +75,7 @@ namespace BTCPayServer.Payments
|
||||||
{
|
{
|
||||||
AdditionalData = new Dictionary<string, JToken>()
|
AdditionalData = new Dictionary<string, JToken>()
|
||||||
{
|
{
|
||||||
{"LNURLP", JToken.FromObject(GetPaymentLink(details.Network, invoice, details.GetPaymentMethodDetails(), invoiceCryptoInfo.Due,
|
{"LNURLP", JToken.FromObject(GetPaymentLink(details.Network, invoice, details.GetPaymentMethodDetails(), invoiceCryptoInfo.GetDue().Value,
|
||||||
serverUrl))}
|
serverUrl))}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -73,7 +73,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||||
decimal due = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility);
|
decimal due = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
due = paymentMethod.Calculate().Due.ToDecimal(MoneyUnit.BTC);
|
due = paymentMethod.Calculate().Due;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|
|
@ -200,7 +200,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||||
if (inv.Name == InvoiceEvent.ReceivedPayment && inv.Invoice.Status == InvoiceStatusLegacy.New && inv.Invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
if (inv.Name == InvoiceEvent.ReceivedPayment && inv.Invoice.Status == InvoiceStatusLegacy.New && inv.Invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||||
{
|
{
|
||||||
var pm = inv.Invoice.GetPaymentMethods().First();
|
var pm = inv.Invoice.GetPaymentMethods().First();
|
||||||
if (pm.Calculate().Due.GetValue(pm.Network as BTCPayNetwork) > 0m)
|
if (pm.Calculate().Due > 0m)
|
||||||
{
|
{
|
||||||
await CreateNewLNInvoiceForBTCPayInvoice(inv.Invoice);
|
await CreateNewLNInvoiceForBTCPayInvoice(inv.Invoice);
|
||||||
}
|
}
|
||||||
|
|
|
@ -302,7 +302,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
var paymentDetails = paymentMethod?.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
|
var paymentDetails = paymentMethod?.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
|
||||||
if (paymentMethod is null || paymentDetails is null || !paymentDetails.PayjoinEnabled)
|
if (paymentMethod is null || paymentDetails is null || !paymentDetails.PayjoinEnabled)
|
||||||
continue;
|
continue;
|
||||||
due = paymentMethod.Calculate().TotalDue - output.Value;
|
due = Money.Coins(paymentMethod.Calculate().TotalDue) - output.Value;
|
||||||
if (due > Money.Zero)
|
if (due > Money.Zero)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -67,7 +67,7 @@ namespace BTCPayServer.Payments
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails,
|
public override string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails,
|
||||||
Money cryptoInfoDue, string serverUri)
|
decimal cryptoInfoDue, string serverUri)
|
||||||
{
|
{
|
||||||
if (!paymentMethodDetails.Activated)
|
if (!paymentMethodDetails.Activated)
|
||||||
{
|
{
|
||||||
|
@ -105,7 +105,7 @@ namespace BTCPayServer.Payments
|
||||||
{
|
{
|
||||||
cryptoInfo.PaymentUrls = new InvoiceCryptoInfo.InvoicePaymentUrls()
|
cryptoInfo.PaymentUrls = new InvoiceCryptoInfo.InvoicePaymentUrls()
|
||||||
{
|
{
|
||||||
BIP21 = GetPaymentLink(details.Network, invoice, details.GetPaymentMethodDetails(), cryptoInfo.Due, serverUrl),
|
BIP21 = GetPaymentLink(details.Network, invoice, details.GetPaymentMethodDetails(), cryptoInfo.GetDue().Value, serverUrl),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Controllers.Greenfield;
|
using BTCPayServer.Controllers.Greenfield;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
|
@ -52,7 +53,7 @@ namespace BTCPayServer.Payments
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails,
|
public override string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails,
|
||||||
Money cryptoInfoDue, string serverUri)
|
decimal cryptoInfoDue, string serverUri)
|
||||||
{
|
{
|
||||||
if (!paymentMethodDetails.Activated)
|
if (!paymentMethodDetails.Activated)
|
||||||
{
|
{
|
||||||
|
@ -92,7 +93,7 @@ namespace BTCPayServer.Payments
|
||||||
{
|
{
|
||||||
invoiceCryptoInfo.PaymentUrls = new InvoiceCryptoInfo.InvoicePaymentUrls()
|
invoiceCryptoInfo.PaymentUrls = new InvoiceCryptoInfo.InvoicePaymentUrls()
|
||||||
{
|
{
|
||||||
BOLT11 = GetPaymentLink(details.Network, invoice, details.GetPaymentMethodDetails(), invoiceCryptoInfo.Due,
|
BOLT11 = GetPaymentLink(details.Network, invoice, details.GetPaymentMethodDetails(), invoiceCryptoInfo.GetDue().Value,
|
||||||
serverUrl)
|
serverUrl)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ namespace BTCPayServer.Payments
|
||||||
public abstract ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network, JToken value);
|
public abstract ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network, JToken value);
|
||||||
public abstract string GetTransactionLink(BTCPayNetworkBase network, string txId);
|
public abstract string GetTransactionLink(BTCPayNetworkBase network, string txId);
|
||||||
public abstract string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails,
|
public abstract string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails,
|
||||||
Money cryptoInfoDue, string serverUri);
|
decimal cryptoInfoDue, string serverUri);
|
||||||
public abstract string InvoiceViewPaymentPartialName { get; }
|
public abstract string InvoiceViewPaymentPartialName { get; }
|
||||||
|
|
||||||
public abstract object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod, bool canModifyStore);
|
public abstract object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod, bool canModifyStore);
|
||||||
|
|
|
@ -89,15 +89,7 @@ namespace BTCPayServer.Plugins.Crowdfund
|
||||||
.GroupBy(entity => entity.Metadata.ItemCode)
|
.GroupBy(entity => entity.Metadata.ItemCode)
|
||||||
.Select(entities =>
|
.Select(entities =>
|
||||||
{
|
{
|
||||||
var total = entities
|
var total = entities.Sum(entity => entity.PaidAmount.Net);
|
||||||
.Sum(entity => entity.GetPayments(true)
|
|
||||||
.Sum(pay =>
|
|
||||||
{
|
|
||||||
var paymentMethodId = pay.GetPaymentMethodId();
|
|
||||||
var value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee;
|
|
||||||
var rate = entity.GetPaymentMethod(paymentMethodId).Rate;
|
|
||||||
return rate * value;
|
|
||||||
}));
|
|
||||||
var itemCode = entities.Key;
|
var itemCode = entities.Key;
|
||||||
var perk = perks.FirstOrDefault(p => p.Id == itemCode);
|
var perk = perks.FirstOrDefault(p => p.Id == itemCode);
|
||||||
return new ItemStats
|
return new ItemStats
|
||||||
|
@ -167,13 +159,7 @@ namespace BTCPayServer.Plugins.Crowdfund
|
||||||
!string.IsNullOrEmpty(entity.Metadata.ItemCode))
|
!string.IsNullOrEmpty(entity.Metadata.ItemCode))
|
||||||
.GroupBy(entity => entity.Metadata.ItemCode)
|
.GroupBy(entity => entity.Metadata.ItemCode)
|
||||||
.ToDictionary(entities => entities.Key, entities =>
|
.ToDictionary(entities => entities.Key, entities =>
|
||||||
entities.Sum(entity => entity.GetPayments(true).Sum(pay =>
|
entities.Sum(entity => entity.PaidAmount.Net));
|
||||||
{
|
|
||||||
var paymentMethodId = pay.GetPaymentMethodId();
|
|
||||||
var value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee;
|
|
||||||
var rate = entity.GetPaymentMethod(paymentMethodId).Rate;
|
|
||||||
return rate * value;
|
|
||||||
})));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var perks = AppService.Parse( settings.PerksTemplate, false);
|
var perks = AppService.Parse( settings.PerksTemplate, false);
|
||||||
|
|
|
@ -119,7 +119,7 @@ namespace BTCPayServer.Plugins.NFC
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
due = new LightMoney(lnPaymentMethod.Calculate().Due);
|
due = LightMoney.Coins(lnPaymentMethod.Calculate().Due);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.MinWithdrawable > due || due > info.MaxWithdrawable)
|
if (info.MinWithdrawable > due || due > info.MaxWithdrawable)
|
||||||
|
@ -135,10 +135,10 @@ namespace BTCPayServer.Plugins.NFC
|
||||||
|
|
||||||
if (lnurlPaymentMethod is not null)
|
if (lnurlPaymentMethod is not null)
|
||||||
{
|
{
|
||||||
Money due;
|
decimal due;
|
||||||
if (invoice.Type == InvoiceType.TopUp && request.Amount is not null)
|
if (invoice.Type == InvoiceType.TopUp && request.Amount is not null)
|
||||||
{
|
{
|
||||||
due = new Money(request.Amount.Value, MoneyUnit.Satoshi);
|
due = new Money(request.Amount.Value, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC);
|
||||||
}
|
}
|
||||||
else if (invoice.Type == InvoiceType.TopUp)
|
else if (invoice.Type == InvoiceType.TopUp)
|
||||||
{
|
{
|
||||||
|
@ -152,7 +152,7 @@ namespace BTCPayServer.Plugins.NFC
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
httpClient = CreateHttpClient(info.Callback);
|
httpClient = CreateHttpClient(info.Callback);
|
||||||
var amount = LightMoney.Satoshis(due.Satoshi);
|
var amount = LightMoney.Coins(due);
|
||||||
var actionPath = Url.Action(nameof(UILNURLController.GetLNURLForInvoice), "UILNURL",
|
var actionPath = Url.Action(nameof(UILNURLController.GetLNURLForInvoice), "UILNURL",
|
||||||
new { invoiceId = request.InvoiceId, cryptoCode = "BTC", amount = amount.MilliSatoshi });
|
new { invoiceId = request.InvoiceId, cryptoCode = "BTC", amount = amount.MilliSatoshi });
|
||||||
var url = Request.GetAbsoluteUri(actionPath);
|
var url = Request.GetAbsoluteUri(actionPath);
|
||||||
|
|
|
@ -14,6 +14,7 @@ using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("BTCPayServer.Tests")]
|
[assembly: InternalsVisibleTo("BTCPayServer.Tests")]
|
||||||
|
|
||||||
namespace BTCPayServer
|
namespace BTCPayServer
|
||||||
{
|
{
|
||||||
class Program
|
class Program
|
||||||
|
|
|
@ -93,7 +93,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||||
{
|
{
|
||||||
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
||||||
model.InvoiceBitcoinUrl = MoneroPaymentType.Instance.GetPaymentLink(network, null,
|
model.InvoiceBitcoinUrl = MoneroPaymentType.Instance.GetPaymentLink(network, null,
|
||||||
new MoneroLikeOnChainPaymentMethodDetails() {DepositAddress = cryptoInfo.Address}, cryptoInfo.Due,
|
new MoneroLikeOnChainPaymentMethodDetails() {DepositAddress = cryptoInfo.Address}, cryptoInfo.GetDue().Value,
|
||||||
null);
|
null);
|
||||||
model.InvoiceBitcoinUrlQR = model.InvoiceBitcoinUrl;
|
model.InvoiceBitcoinUrlQR = model.InvoiceBitcoinUrl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,10 +49,10 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
||||||
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
|
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails, Money cryptoInfoDue, string serverUri)
|
public override string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails, decimal cryptoInfoDue, string serverUri)
|
||||||
{
|
{
|
||||||
return paymentMethodDetails.Activated
|
return paymentMethodDetails.Activated
|
||||||
? $"{(network as MoneroLikeSpecificBtcPayNetwork).UriScheme}:{paymentMethodDetails.GetPaymentDestination()}?tx_amount={cryptoInfoDue.ToDecimal(MoneyUnit.BTC)}"
|
? $"{(network as MoneroLikeSpecificBtcPayNetwork).UriScheme}:{paymentMethodDetails.GetPaymentDestination()}?tx_amount={cryptoInfoDue}"
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,16 +119,16 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
|
||||||
private async Task ReceivedPayment(InvoiceEntity invoice, PaymentEntity payment)
|
private async Task ReceivedPayment(InvoiceEntity invoice, PaymentEntity payment)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
$"Invoice {invoice.Id} received payment {payment.GetCryptoPaymentData().GetValue()} {payment.GetCryptoCode()} {payment.GetCryptoPaymentData().GetPaymentId()}");
|
$"Invoice {invoice.Id} received payment {payment.GetCryptoPaymentData().GetValue()} {payment.Currency} {payment.GetCryptoPaymentData().GetPaymentId()}");
|
||||||
var paymentData = (MoneroLikePaymentData)payment.GetCryptoPaymentData();
|
var paymentData = (MoneroLikePaymentData)payment.GetCryptoPaymentData();
|
||||||
var paymentMethod = invoice.GetPaymentMethod(payment.Network, MoneroPaymentType.Instance);
|
var paymentMethod = invoice.GetPaymentMethod(payment.Network, MoneroPaymentType.Instance);
|
||||||
if (paymentMethod != null &&
|
if (paymentMethod != null &&
|
||||||
paymentMethod.GetPaymentMethodDetails() is MoneroLikeOnChainPaymentMethodDetails monero &&
|
paymentMethod.GetPaymentMethodDetails() is MoneroLikeOnChainPaymentMethodDetails monero &&
|
||||||
monero.Activated &&
|
monero.Activated &&
|
||||||
monero.GetPaymentDestination() == paymentData.GetDestination() &&
|
monero.GetPaymentDestination() == paymentData.GetDestination() &&
|
||||||
paymentMethod.Calculate().Due > Money.Zero)
|
paymentMethod.Calculate().Due > 0.0m)
|
||||||
{
|
{
|
||||||
var walletClient = _moneroRpcProvider.WalletRpcClients[payment.GetCryptoCode()];
|
var walletClient = _moneroRpcProvider.WalletRpcClients[payment.Currency];
|
||||||
|
|
||||||
var address = await walletClient.SendCommandAsync<CreateAddressRequest, CreateAddressResponse>(
|
var address = await walletClient.SendCommandAsync<CreateAddressRequest, CreateAddressResponse>(
|
||||||
"create_address",
|
"create_address",
|
||||||
|
|
|
@ -93,7 +93,7 @@ namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||||
{
|
{
|
||||||
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
||||||
model.InvoiceBitcoinUrl = ZcashPaymentType.Instance.GetPaymentLink(network, null,
|
model.InvoiceBitcoinUrl = ZcashPaymentType.Instance.GetPaymentLink(network, null,
|
||||||
new ZcashLikeOnChainPaymentMethodDetails() {DepositAddress = cryptoInfo.Address}, cryptoInfo.Due,
|
new ZcashLikeOnChainPaymentMethodDetails() {DepositAddress = cryptoInfo.Address}, cryptoInfo.GetDue().Value,
|
||||||
null);
|
null);
|
||||||
model.InvoiceBitcoinUrlQR = model.InvoiceBitcoinUrl;
|
model.InvoiceBitcoinUrlQR = model.InvoiceBitcoinUrl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,10 +49,10 @@ namespace BTCPayServer.Services.Altcoins.Zcash.Payments
|
||||||
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
|
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails, Money cryptoInfoDue, string serverUri)
|
public override string GetPaymentLink(BTCPayNetworkBase network, InvoiceEntity invoice, IPaymentMethodDetails paymentMethodDetails, decimal cryptoInfoDue, string serverUri)
|
||||||
{
|
{
|
||||||
return paymentMethodDetails.Activated
|
return paymentMethodDetails.Activated
|
||||||
? $"{(network as ZcashLikeSpecificBtcPayNetwork).UriScheme}:{paymentMethodDetails.GetPaymentDestination()}?amount={cryptoInfoDue.ToDecimal(MoneyUnit.BTC)}"
|
? $"{(network as ZcashLikeSpecificBtcPayNetwork).UriScheme}:{paymentMethodDetails.GetPaymentDestination()}?amount={cryptoInfoDue}"
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,16 +114,16 @@ namespace BTCPayServer.Services.Altcoins.Zcash.Services
|
||||||
private async Task ReceivedPayment(InvoiceEntity invoice, PaymentEntity payment)
|
private async Task ReceivedPayment(InvoiceEntity invoice, PaymentEntity payment)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
$"Invoice {invoice.Id} received payment {payment.GetCryptoPaymentData().GetValue()} {payment.GetCryptoCode()} {payment.GetCryptoPaymentData().GetPaymentId()}");
|
$"Invoice {invoice.Id} received payment {payment.GetCryptoPaymentData().GetValue()} {payment.Currency} {payment.GetCryptoPaymentData().GetPaymentId()}");
|
||||||
var paymentData = (ZcashLikePaymentData)payment.GetCryptoPaymentData();
|
var paymentData = (ZcashLikePaymentData)payment.GetCryptoPaymentData();
|
||||||
var paymentMethod = invoice.GetPaymentMethod(payment.Network, ZcashPaymentType.Instance);
|
var paymentMethod = invoice.GetPaymentMethod(payment.Network, ZcashPaymentType.Instance);
|
||||||
if (paymentMethod != null &&
|
if (paymentMethod != null &&
|
||||||
paymentMethod.GetPaymentMethodDetails() is ZcashLikeOnChainPaymentMethodDetails Zcash &&
|
paymentMethod.GetPaymentMethodDetails() is ZcashLikeOnChainPaymentMethodDetails Zcash &&
|
||||||
Zcash.Activated &&
|
Zcash.Activated &&
|
||||||
Zcash.GetPaymentDestination() == paymentData.GetDestination() &&
|
Zcash.GetPaymentDestination() == paymentData.GetDestination() &&
|
||||||
paymentMethod.Calculate().Due > Money.Zero)
|
paymentMethod.Calculate().Due > 0.0m)
|
||||||
{
|
{
|
||||||
var walletClient = _ZcashRpcProvider.WalletRpcClients[payment.GetCryptoCode()];
|
var walletClient = _ZcashRpcProvider.WalletRpcClients[payment.Currency];
|
||||||
|
|
||||||
var address = await walletClient.SendCommandAsync<CreateAddressRequest, CreateAddressResponse>(
|
var address = await walletClient.SendCommandAsync<CreateAddressRequest, CreateAddressResponse>(
|
||||||
"create_address",
|
"create_address",
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace BTCPayServer.Services.Apps
|
||||||
await _HubContext.Clients.Group(appId).SendCoreAsync(AppHub.PaymentReceived, new object[]
|
await _HubContext.Clients.Group(appId).SendCoreAsync(AppHub.PaymentReceived, new object[]
|
||||||
{
|
{
|
||||||
data.GetValue(),
|
data.GetValue(),
|
||||||
invoiceEvent.Payment.GetCryptoCode(),
|
invoiceEvent.Payment.Currency,
|
||||||
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType?.ToString()
|
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType?.ToString()
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,18 +183,11 @@ namespace BTCPayServer.Services.Apps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{;
|
||||||
var fiatPrice = e.GetPayments(true).Sum(pay =>
|
|
||||||
{
|
|
||||||
var paymentMethodId = pay.GetPaymentMethodId();
|
|
||||||
var value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee;
|
|
||||||
var rate = e.GetPaymentMethod(paymentMethodId).Rate;
|
|
||||||
return rate * value;
|
|
||||||
});
|
|
||||||
res.Add(new InvoiceStatsItem
|
res.Add(new InvoiceStatsItem
|
||||||
{
|
{
|
||||||
ItemCode = e.Metadata.ItemCode,
|
ItemCode = e.Metadata.ItemCode,
|
||||||
FiatPrice = fiatPrice,
|
FiatPrice = e.PaidAmount.Net,
|
||||||
Date = e.InvoiceTime.Date
|
Date = e.InvoiceTime.Date
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
19
BTCPayServer/Services/Invoices/Amounts.cs
Normal file
19
BTCPayServer/Services/Invoices/Amounts.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
namespace BTCPayServer.Services.Invoices
|
||||||
|
{
|
||||||
|
public class Amounts
|
||||||
|
{
|
||||||
|
public string Currency { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// An amount with fee included
|
||||||
|
/// </summary>
|
||||||
|
public decimal Gross { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// An amount without fee included
|
||||||
|
/// </summary>
|
||||||
|
public decimal Net { get; set; }
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Currency}: Net={Net}, Gross={Gross}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,23 +64,19 @@ namespace BTCPayServer.Services.Invoices.Export
|
||||||
{
|
{
|
||||||
foreach (var payment in payments)
|
foreach (var payment in payments)
|
||||||
{
|
{
|
||||||
var cryptoCode = payment.GetPaymentMethodId().CryptoCode;
|
|
||||||
var pdata = payment.GetCryptoPaymentData();
|
var pdata = payment.GetCryptoPaymentData();
|
||||||
|
invoiceDue -= payment.InvoicePaidAmount.Net;
|
||||||
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId());
|
|
||||||
var paidAfterNetworkFees = pdata.GetValue() - payment.NetworkFee;
|
|
||||||
invoiceDue -= paidAfterNetworkFees * pmethod.Rate;
|
|
||||||
|
|
||||||
var target = new ExportInvoiceHolder
|
var target = new ExportInvoiceHolder
|
||||||
{
|
{
|
||||||
ReceivedDate = payment.ReceivedTime.UtcDateTime,
|
ReceivedDate = payment.ReceivedTime.UtcDateTime,
|
||||||
PaymentId = pdata.GetPaymentId(),
|
PaymentId = pdata.GetPaymentId(),
|
||||||
CryptoCode = cryptoCode,
|
CryptoCode = payment.Currency,
|
||||||
ConversionRate = pmethod.Rate,
|
ConversionRate = payment.Rate,
|
||||||
PaymentType = payment.GetPaymentMethodId().PaymentType.ToPrettyString(),
|
PaymentType = payment.GetPaymentMethodId().PaymentType.ToPrettyString(),
|
||||||
Destination = pdata.GetDestination(),
|
Destination = pdata.GetDestination(),
|
||||||
Paid = pdata.GetValue().ToString(CultureInfo.InvariantCulture),
|
Paid = payment.PaidAmount.Gross.ToString(CultureInfo.InvariantCulture),
|
||||||
PaidCurrency = Math.Round(pdata.GetValue() * pmethod.Rate, currency.NumberDecimalDigits).ToString(CultureInfo.InvariantCulture),
|
PaidCurrency = Math.Round(payment.InvoicePaidAmount.Gross, currency.NumberDecimalDigits).ToString(CultureInfo.InvariantCulture),
|
||||||
// Adding NetworkFee because Paid doesn't take into account network fees
|
// Adding NetworkFee because Paid doesn't take into account network fees
|
||||||
// so if fee is 10000 satoshis, customer can essentially send infinite number of tx
|
// so if fee is 10000 satoshis, customer can essentially send infinite number of tx
|
||||||
// and merchant effectivelly would receive 0 BTC, invoice won't be paid
|
// and merchant effectivelly would receive 0 BTC, invoice won't be paid
|
||||||
|
|
|
@ -378,6 +378,82 @@ namespace BTCPayServer.Services.Invoices
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public Dictionary<string, decimal> Rates
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
public void UpdateTotals()
|
||||||
|
{
|
||||||
|
Rates = new Dictionary<string, decimal>();
|
||||||
|
foreach (var p in GetPaymentMethods())
|
||||||
|
{
|
||||||
|
Rates.TryAdd(p.Currency, p.Rate);
|
||||||
|
}
|
||||||
|
PaidAmount = new Amounts()
|
||||||
|
{
|
||||||
|
Currency = Currency
|
||||||
|
};
|
||||||
|
foreach (var payment in GetPayments(false))
|
||||||
|
{
|
||||||
|
payment.Rate = Rates[payment.Currency];
|
||||||
|
payment.InvoiceEntity = this;
|
||||||
|
payment.UpdateAmounts();
|
||||||
|
if (payment.Accounted)
|
||||||
|
{
|
||||||
|
PaidAmount.Gross += payment.InvoicePaidAmount.Gross;
|
||||||
|
PaidAmount.Net += payment.InvoicePaidAmount.Net;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetDue = Price - PaidAmount.Net;
|
||||||
|
MinimumNetDue = Price * (1.0m - ((decimal)PaymentTolerance / 100.0m)) - PaidAmount.Net;
|
||||||
|
PaidFee = PaidAmount.Gross - PaidAmount.Net;
|
||||||
|
if (NetDue < 0.0m)
|
||||||
|
{
|
||||||
|
// If any payment method exactly pay the invoice, the overpayment is caused by
|
||||||
|
// rounding limitation of the underlying payment method.
|
||||||
|
// Document this overpayment as dust, and set the net due to 0
|
||||||
|
if (GetPaymentMethods().Any(p => p.Calculate().DueUncapped == 0.0m))
|
||||||
|
{
|
||||||
|
Dust = -NetDue;
|
||||||
|
NetDue = 0.0m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overpaid amount caused by payment method
|
||||||
|
/// Example: If you need to pay 124.4 sats, the on-chain payment need to be technically rounded to 125 sats, the extra 0.6 sats shouldn't be considered an over payment.
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public decimal Dust { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The due to consider the invoice paid (can be negative if over payment)
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public decimal NetDue
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Minumum due to consider the invoice paid (can be negative if overpaid)
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public decimal MinimumNetDue { get; set; }
|
||||||
|
public bool IsUnderPaid => MinimumNetDue > 0;
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool IsOverPaid => NetDue < 0;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total of network fee paid by accounted payments
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public decimal PaidFee { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public InvoiceStatusLegacy Status { get; set; }
|
public InvoiceStatusLegacy Status { get; set; }
|
||||||
[JsonProperty(PropertyName = "status")]
|
[JsonProperty(PropertyName = "status")]
|
||||||
|
@ -399,7 +475,7 @@ namespace BTCPayServer.Services.Invoices
|
||||||
}
|
}
|
||||||
public List<PaymentEntity> GetPayments(string cryptoCode, bool accountedOnly)
|
public List<PaymentEntity> GetPayments(string cryptoCode, bool accountedOnly)
|
||||||
{
|
{
|
||||||
return GetPayments(accountedOnly).Where(p => p.CryptoCode == cryptoCode).ToList();
|
return GetPayments(accountedOnly).Where(p => p.Currency == cryptoCode).ToList();
|
||||||
}
|
}
|
||||||
public List<PaymentEntity> GetPayments(BTCPayNetworkBase network, bool accountedOnly)
|
public List<PaymentEntity> GetPayments(BTCPayNetworkBase network, bool accountedOnly)
|
||||||
{
|
{
|
||||||
|
@ -554,13 +630,13 @@ namespace BTCPayServer.Services.Invoices
|
||||||
if (paymentId.PaymentType == PaymentTypes.BTCLike)
|
if (paymentId.PaymentType == PaymentTypes.BTCLike)
|
||||||
{
|
{
|
||||||
var minerInfo = new MinerFeeInfo();
|
var minerInfo = new MinerFeeInfo();
|
||||||
minerInfo.TotalFee = accounting.NetworkFee.Satoshi;
|
minerInfo.TotalFee = accounting.ToSmallestUnit(accounting.NetworkFee);
|
||||||
minerInfo.SatoshiPerBytes = ((BitcoinLikeOnChainPaymentMethod)details).FeeRate
|
minerInfo.SatoshiPerBytes = ((BitcoinLikeOnChainPaymentMethod)details).FeeRate
|
||||||
.GetFee(1).Satoshi;
|
.GetFee(1).Satoshi;
|
||||||
dto.MinerFees.TryAdd(cryptoInfo.CryptoCode, minerInfo);
|
dto.MinerFees.TryAdd(cryptoInfo.CryptoCode, minerInfo);
|
||||||
|
|
||||||
#pragma warning disable 618
|
#pragma warning disable 618
|
||||||
if (info.CryptoCode == "BTC")
|
if (info.Currency == "BTC")
|
||||||
{
|
{
|
||||||
dto.BTCPrice = cryptoInfo.Price;
|
dto.BTCPrice = cryptoInfo.Price;
|
||||||
dto.Rate = cryptoInfo.Rate;
|
dto.Rate = cryptoInfo.Rate;
|
||||||
|
@ -576,8 +652,8 @@ namespace BTCPayServer.Services.Invoices
|
||||||
|
|
||||||
dto.CryptoInfo.Add(cryptoInfo);
|
dto.CryptoInfo.Add(cryptoInfo);
|
||||||
dto.PaymentCodes.Add(paymentId.ToString(), cryptoInfo.PaymentUrls);
|
dto.PaymentCodes.Add(paymentId.ToString(), cryptoInfo.PaymentUrls);
|
||||||
dto.PaymentSubtotals.Add(paymentId.ToString(), subtotalPrice.Satoshi);
|
dto.PaymentSubtotals.Add(paymentId.ToString(), accounting.ToSmallestUnit(subtotalPrice));
|
||||||
dto.PaymentTotals.Add(paymentId.ToString(), accounting.TotalDue.Satoshi);
|
dto.PaymentTotals.Add(paymentId.ToString(), accounting.ToSmallestUnit(accounting.TotalDue));
|
||||||
dto.SupportedTransactionCurrencies.TryAdd(cryptoCode, new InvoiceSupportedTransactionCurrency()
|
dto.SupportedTransactionCurrencies.TryAdd(cryptoCode, new InvoiceSupportedTransactionCurrency()
|
||||||
{
|
{
|
||||||
Enabled = true
|
Enabled = true
|
||||||
|
@ -640,12 +716,12 @@ namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
r.CryptoCode = paymentMethodId.CryptoCode;
|
r.Currency = paymentMethodId.CryptoCode;
|
||||||
r.PaymentType = paymentMethodId.PaymentType.ToString();
|
r.PaymentType = paymentMethodId.PaymentType.ToString();
|
||||||
r.ParentEntity = this;
|
r.ParentEntity = this;
|
||||||
if (Networks != null)
|
if (Networks != null)
|
||||||
{
|
{
|
||||||
r.Network = Networks.GetNetwork<BTCPayNetworkBase>(r.CryptoCode);
|
r.Network = Networks.GetNetwork<BTCPayNetworkBase>(r.Currency);
|
||||||
if (r.Network is null)
|
if (r.Network is null)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -671,7 +747,7 @@ namespace BTCPayServer.Services.Invoices
|
||||||
foreach (var v in paymentMethods)
|
foreach (var v in paymentMethods)
|
||||||
{
|
{
|
||||||
var clone = serializer.ToObject<PaymentMethod>(serializer.ToString(v));
|
var clone = serializer.ToObject<PaymentMethod>(serializer.ToString(v));
|
||||||
clone.CryptoCode = null;
|
clone.Currency = null;
|
||||||
clone.PaymentType = null;
|
clone.PaymentType = null;
|
||||||
obj.Add(new JProperty(v.GetId().ToString(), JObject.Parse(serializer.ToString(clone))));
|
obj.Add(new JProperty(v.GetId().ToString(), JObject.Parse(serializer.ToString(clone))));
|
||||||
}
|
}
|
||||||
|
@ -681,6 +757,7 @@ namespace BTCPayServer.Services.Invoices
|
||||||
cryptoData.ParentEntity = this;
|
cryptoData.ParentEntity = this;
|
||||||
}
|
}
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
|
UpdateTotals();
|
||||||
}
|
}
|
||||||
|
|
||||||
public InvoiceState GetInvoiceState()
|
public InvoiceState GetInvoiceState()
|
||||||
|
@ -748,6 +825,8 @@ namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
return Type == InvoiceType.TopUp && Price == 0.0m;
|
return Type == InvoiceType.TopUp && Price == 0.0m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Amounts PaidAmount { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum InvoiceStatusLegacy
|
public enum InvoiceStatusLegacy
|
||||||
|
@ -897,30 +976,31 @@ namespace BTCPayServer.Services.Invoices
|
||||||
|
|
||||||
public class PaymentMethodAccounting
|
public class PaymentMethodAccounting
|
||||||
{
|
{
|
||||||
|
public int Divisibility { get; set; }
|
||||||
/// <summary>Total amount of this invoice</summary>
|
/// <summary>Total amount of this invoice</summary>
|
||||||
public Money TotalDue { get; set; }
|
public decimal TotalDue { get; set; }
|
||||||
|
|
||||||
/// <summary>Amount of crypto remaining to pay this invoice</summary>
|
/// <summary>Amount of crypto remaining to pay this invoice</summary>
|
||||||
public Money Due { get; set; }
|
public decimal Due { get; set; }
|
||||||
|
|
||||||
/// <summary>Same as Due, can be negative</summary>
|
/// <summary>Same as Due, can be negative</summary>
|
||||||
public Money DueUncapped { get; set; }
|
public decimal DueUncapped { get; set; }
|
||||||
|
|
||||||
/// <summary>If DueUncapped is negative, that means user overpaid invoice</summary>
|
/// <summary>If DueUncapped is negative, that means user overpaid invoice</summary>
|
||||||
public Money OverpaidHelper
|
public decimal OverpaidHelper
|
||||||
{
|
{
|
||||||
get { return DueUncapped > Money.Zero ? Money.Zero : -DueUncapped; }
|
get { return DueUncapped > 0.0m ? 0.0m : -DueUncapped; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total amount of the invoice paid after conversion to this crypto currency
|
/// Total amount of the invoice paid after conversion to this crypto currency
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Money Paid { get; set; }
|
public decimal Paid { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total amount of the invoice paid in this currency
|
/// Total amount of the invoice paid in this currency
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Money CryptoPaid { get; set; }
|
public decimal CryptoPaid { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Number of transactions required to pay
|
/// Number of transactions required to pay
|
||||||
|
@ -934,15 +1014,25 @@ namespace BTCPayServer.Services.Invoices
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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 decimal NetworkFee { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total amount of network fee to pay to the invoice
|
/// Total amount of network fee to pay to the invoice
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Money NetworkFeeAlreadyPaid { get; set; }
|
public decimal NetworkFeeAlreadyPaid { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Minimum required to be paid in order to accept invoice as paid
|
/// Minimum required to be paid in order to accept invoice as paid
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Money MinimumTotalDue { get; set; }
|
public decimal MinimumTotalDue { get; set; }
|
||||||
|
|
||||||
|
public decimal ToSmallestUnit(decimal v)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Divisibility; i++)
|
||||||
|
{
|
||||||
|
v *= 10.0m;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
public string ShowMoney(decimal v) => MoneyExtensions.ShowMoney(v, Divisibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IPaymentMethod
|
public interface IPaymentMethod
|
||||||
|
@ -959,8 +1049,7 @@ namespace BTCPayServer.Services.Invoices
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public BTCPayNetworkBase Network { get; set; }
|
public BTCPayNetworkBase Network { get; set; }
|
||||||
[JsonProperty(PropertyName = "cryptoCode", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
[JsonProperty(PropertyName = "cryptoCode", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
[Obsolete("Use GetId().CryptoCode instead")]
|
public string Currency { get; set; }
|
||||||
public string CryptoCode { get; set; }
|
|
||||||
[JsonProperty(PropertyName = "paymentType", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
[JsonProperty(PropertyName = "paymentType", DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
[Obsolete("Use GetId().PaymentType instead")]
|
[Obsolete("Use GetId().PaymentType instead")]
|
||||||
public string PaymentType { get; set; }
|
public string PaymentType { get; set; }
|
||||||
|
@ -975,14 +1064,14 @@ namespace BTCPayServer.Services.Invoices
|
||||||
public PaymentMethodId GetId()
|
public PaymentMethodId GetId()
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
return new PaymentMethodId(CryptoCode, string.IsNullOrEmpty(PaymentType) ? PaymentTypes.BTCLike : PaymentTypes.Parse(PaymentType));
|
return new PaymentMethodId(Currency, string.IsNullOrEmpty(PaymentType) ? PaymentTypes.BTCLike : PaymentTypes.Parse(PaymentType));
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetId(PaymentMethodId id)
|
public void SetId(PaymentMethodId id)
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
CryptoCode = id.CryptoCode;
|
Currency = id.CryptoCode;
|
||||||
PaymentType = id.PaymentType.ToString();
|
PaymentType = id.PaymentType.ToString();
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
|
@ -1053,7 +1142,6 @@ namespace BTCPayServer.Services.Invoices
|
||||||
DepositAddress = bitcoinPaymentMethod.DepositAddress;
|
DepositAddress = bitcoinPaymentMethod.DepositAddress;
|
||||||
}
|
}
|
||||||
PaymentMethodDetails = JObject.Parse(paymentMethod.GetPaymentType().SerializePaymentMethodDetails(Network, paymentMethod));
|
PaymentMethodDetails = JObject.Parse(paymentMethod.GetPaymentType().SerializePaymentMethodDetails(Network, paymentMethod));
|
||||||
|
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -1068,86 +1156,58 @@ namespace BTCPayServer.Services.Invoices
|
||||||
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).DepositAddress")]
|
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).DepositAddress")]
|
||||||
public string DepositAddress { get; set; }
|
public string DepositAddress { get; set; }
|
||||||
|
|
||||||
public PaymentMethodAccounting Calculate(Func<PaymentEntity, bool> paymentPredicate = null)
|
public PaymentMethodAccounting Calculate()
|
||||||
{
|
{
|
||||||
paymentPredicate = paymentPredicate ?? new Func<PaymentEntity, bool>((p) => true);
|
var i = ParentEntity;
|
||||||
var paymentMethods = ParentEntity.GetPaymentMethods();
|
|
||||||
|
|
||||||
var totalDue = ParentEntity.Price / Rate;
|
|
||||||
var paid = 0m;
|
|
||||||
var cryptoPaid = 0.0m;
|
|
||||||
|
|
||||||
int precision = Network?.Divisibility ?? 8;
|
int precision = Network?.Divisibility ?? 8;
|
||||||
|
|
||||||
var totalDueNoNetworkCost = Coins(Extensions.RoundUp(totalDue, precision));
|
|
||||||
bool paidEnough = paid >= Extensions.RoundUp(totalDue, precision);
|
|
||||||
int txRequired = 0;
|
|
||||||
decimal networkFeeAlreadyPaid = 0.0m;
|
|
||||||
_ = ParentEntity.GetPayments(true)
|
|
||||||
.Where(p => paymentPredicate(p))
|
|
||||||
.OrderBy(p => p.ReceivedTime)
|
|
||||||
.Select(_ =>
|
|
||||||
{
|
|
||||||
var txFee = _.GetValue(paymentMethods, GetId(), _.NetworkFee, precision);
|
|
||||||
networkFeeAlreadyPaid += txFee;
|
|
||||||
paid += _.GetValue(paymentMethods, GetId(), null, precision);
|
|
||||||
if (!paidEnough)
|
|
||||||
{
|
|
||||||
totalDue += txFee;
|
|
||||||
}
|
|
||||||
|
|
||||||
paidEnough |= Extensions.RoundUp(paid, precision) >= Extensions.RoundUp(totalDue, precision);
|
|
||||||
if (GetId() == _.GetPaymentMethodId())
|
|
||||||
{
|
|
||||||
cryptoPaid += _.GetCryptoPaymentData().GetValue();
|
|
||||||
txRequired++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _;
|
|
||||||
}).ToArray();
|
|
||||||
|
|
||||||
var accounting = new PaymentMethodAccounting();
|
var accounting = new PaymentMethodAccounting();
|
||||||
accounting.TxCount = txRequired;
|
var thisPaymentMethodPayments = i.GetPayments(true).Where(p => GetId() == p.GetPaymentMethodId()).ToList();
|
||||||
if (!paidEnough)
|
accounting.TxCount = thisPaymentMethodPayments.Count;
|
||||||
|
accounting.TxRequired = accounting.TxCount;
|
||||||
|
var grossDue = i.Price + i.PaidFee;
|
||||||
|
if (i.MinimumNetDue > 0.0m)
|
||||||
{
|
{
|
||||||
txRequired++;
|
accounting.TxRequired++;
|
||||||
totalDue += GetTxFee();
|
grossDue += Rate * (GetPaymentMethodDetails()?.GetNextNetworkFee() ?? 0m);
|
||||||
}
|
}
|
||||||
|
accounting.Divisibility = precision;
|
||||||
|
accounting.TotalDue = Coins(grossDue / Rate, precision);
|
||||||
|
accounting.Paid = Coins(i.PaidAmount.Gross / Rate, precision);
|
||||||
|
accounting.CryptoPaid = Coins(thisPaymentMethodPayments.Sum(p => p.PaidAmount.Gross), precision);
|
||||||
|
|
||||||
accounting.TotalDue = Coins(Extensions.RoundUp(totalDue, precision));
|
// This one deal with the fact where it might looks like a slight over payment due to the dust of another payment method.
|
||||||
accounting.Paid = Coins(Extensions.RoundUp(paid, precision));
|
// So if we detect the NetDue is zero, just cap dueUncapped to 0
|
||||||
accounting.TxRequired = txRequired;
|
var dueUncapped = i.NetDue == 0.0m ? 0.0m : grossDue - i.PaidAmount.Gross;
|
||||||
accounting.CryptoPaid = Coins(Extensions.RoundUp(cryptoPaid, precision));
|
accounting.DueUncapped = Coins(dueUncapped / Rate, precision);
|
||||||
accounting.Due = Money.Max(accounting.TotalDue - accounting.Paid, Money.Zero);
|
accounting.Due = Max(accounting.DueUncapped, 0.0m);
|
||||||
accounting.DueUncapped = accounting.TotalDue - accounting.Paid;
|
|
||||||
accounting.NetworkFee = accounting.TotalDue - totalDueNoNetworkCost;
|
accounting.NetworkFee = Coins((grossDue - i.Price) / Rate, precision);
|
||||||
accounting.NetworkFeeAlreadyPaid = Coins(Extensions.RoundUp(networkFeeAlreadyPaid, precision));
|
accounting.NetworkFeeAlreadyPaid = Coins(i.PaidFee / Rate, precision);
|
||||||
// If the total due is 0, there is no payment tolerance to calculate
|
|
||||||
var minimumTotalDueSatoshi = accounting.TotalDue.Satoshi == 0
|
accounting.MinimumTotalDue = Max(Smallest(precision), Coins((grossDue * (1.0m - ((decimal)i.PaymentTolerance / 100.0m))) / Rate, precision));
|
||||||
? 0
|
|
||||||
: Math.Max(1.0m,
|
|
||||||
accounting.TotalDue.Satoshi * (1.0m - ((decimal)ParentEntity.PaymentTolerance / 100.0m)));
|
|
||||||
accounting.MinimumTotalDue = Money.Satoshis(minimumTotalDueSatoshi);
|
|
||||||
return accounting;
|
return accounting;
|
||||||
}
|
}
|
||||||
|
|
||||||
const decimal MaxCoinValue = decimal.MaxValue / 1_0000_0000m;
|
private decimal Smallest(int precision)
|
||||||
private Money Coins(decimal v)
|
|
||||||
{
|
{
|
||||||
if (v > MaxCoinValue)
|
decimal a = 1.0m;
|
||||||
v = MaxCoinValue;
|
for (int i = 0; i < precision; i++)
|
||||||
// Clamp the value to not crash on degenerate invoices
|
{
|
||||||
v *= 1_0000_0000m;
|
a /= 10.0m;
|
||||||
if (v > long.MaxValue)
|
}
|
||||||
return Money.Satoshis(long.MaxValue);
|
return a;
|
||||||
if (v < long.MinValue)
|
|
||||||
return Money.Satoshis(long.MinValue);
|
|
||||||
return Money.Satoshis(v);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private decimal GetTxFee()
|
decimal Max(decimal a, decimal b) => a > b ? a : b;
|
||||||
|
|
||||||
|
const decimal MaxCoinValue = decimal.MaxValue / 1_0000_0000m;
|
||||||
|
internal static decimal Coins(decimal v, int precision)
|
||||||
{
|
{
|
||||||
return GetPaymentMethodDetails()?.GetNextNetworkFee() ?? 0m;
|
v = Extensions.RoundUp(v, precision);
|
||||||
|
// Clamp the value to not crash on degenerate invoices
|
||||||
|
if (v > MaxCoinValue)
|
||||||
|
v = MaxCoinValue;
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1209,19 +1269,60 @@ namespace BTCPayServer.Services.Invoices
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string _Currency;
|
||||||
[Obsolete("Use GetpaymentMethodId().CryptoCode instead")]
|
[JsonProperty("cryptoCode")]
|
||||||
public string CryptoCode
|
public string Currency
|
||||||
{
|
{
|
||||||
get;
|
get
|
||||||
set;
|
{
|
||||||
|
return _Currency ?? "BTC";
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_Currency = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("Use GetCryptoPaymentData() instead")]
|
[Obsolete("Use GetCryptoPaymentData() instead")]
|
||||||
public string CryptoPaymentData { get; set; }
|
public string CryptoPaymentData { get; set; }
|
||||||
[Obsolete("Use GetpaymentMethodId().PaymentType instead")]
|
[Obsolete("Use GetpaymentMethodId().PaymentType instead")]
|
||||||
public string CryptoPaymentDataType { get; set; }
|
public string CryptoPaymentDataType { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public decimal Rate { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
/// <summary>
|
||||||
|
public string InvoiceCurrency => InvoiceEntity.Currency;
|
||||||
|
/// The amount paid by this payment in the <see cref="Currency"/>
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public Amounts PaidAmount { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The amount paid by this payment in the <see cref="InvoiceCurrency"/>
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public Amounts InvoicePaidAmount { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public InvoiceEntity InvoiceEntity { get; set; }
|
||||||
|
|
||||||
|
public void UpdateAmounts()
|
||||||
|
{
|
||||||
|
var pd = GetCryptoPaymentData();
|
||||||
|
if (pd is null)
|
||||||
|
return;
|
||||||
|
var value = pd.GetValue();
|
||||||
|
PaidAmount = new Amounts()
|
||||||
|
{
|
||||||
|
Currency = Currency,
|
||||||
|
Gross = value,
|
||||||
|
Net = value - NetworkFee
|
||||||
|
};
|
||||||
|
InvoicePaidAmount = new Amounts()
|
||||||
|
{
|
||||||
|
Currency = InvoiceCurrency,
|
||||||
|
Gross = PaidAmount.Gross * Rate,
|
||||||
|
Net = PaidAmount.Net * Rate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public CryptoPaymentData GetCryptoPaymentData()
|
public CryptoPaymentData GetCryptoPaymentData()
|
||||||
{
|
{
|
||||||
|
@ -1280,21 +1381,6 @@ namespace BTCPayServer.Services.Invoices
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
internal decimal GetValue(PaymentMethodDictionary paymentMethods, PaymentMethodId paymentMethodId, decimal? value, int precision)
|
|
||||||
{
|
|
||||||
|
|
||||||
value = value ?? this.GetCryptoPaymentData().GetValue();
|
|
||||||
var to = paymentMethodId;
|
|
||||||
var from = this.GetPaymentMethodId();
|
|
||||||
if (to == from)
|
|
||||||
return decimal.Round(value.Value, precision);
|
|
||||||
var fromRate = paymentMethods[from].Rate;
|
|
||||||
var toRate = paymentMethods[to].Rate;
|
|
||||||
|
|
||||||
var fiatValue = fromRate * decimal.Round(value.Value, precision);
|
|
||||||
var otherCurrencyValue = toRate == 0 ? 0.0m : fiatValue / toRate;
|
|
||||||
return otherCurrencyValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PaymentMethodId GetPaymentMethodId()
|
public PaymentMethodId GetPaymentMethodId()
|
||||||
{
|
{
|
||||||
|
@ -1308,16 +1394,9 @@ namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new PaymentMethodId(CryptoCode ?? "BTC", paymentType);
|
return new PaymentMethodId(Currency ?? "BTC", paymentType);
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetCryptoCode()
|
|
||||||
{
|
|
||||||
#pragma warning disable CS0618
|
|
||||||
return CryptoCode ?? "BTC";
|
|
||||||
#pragma warning restore CS0618
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A record of a payment
|
/// A record of a payment
|
||||||
|
|
|
@ -12,7 +12,6 @@ using BTCPayServer.Logging;
|
||||||
using BTCPayServer.Models.InvoicingModels;
|
using BTCPayServer.Models.InvoicingModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
@ -30,16 +29,13 @@ namespace BTCPayServer.Services.Invoices
|
||||||
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializerSettings);
|
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializerSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Logs Logs { get; }
|
|
||||||
|
|
||||||
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||||
|
|
||||||
public InvoiceRepository(ApplicationDbContextFactory contextFactory,
|
public InvoiceRepository(ApplicationDbContextFactory contextFactory,
|
||||||
BTCPayNetworkProvider networks, EventAggregator eventAggregator, Logs logs)
|
BTCPayNetworkProvider networks, EventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
Logs = logs;
|
|
||||||
_applicationDbContextFactory = contextFactory;
|
_applicationDbContextFactory = contextFactory;
|
||||||
_btcPayNetworkProvider = networks;
|
_btcPayNetworkProvider = networks;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
|
@ -54,14 +50,20 @@ namespace BTCPayServer.Services.Invoices
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public InvoiceEntity CreateNewInvoice()
|
public InvoiceEntity CreateNewInvoice(string storeId)
|
||||||
{
|
{
|
||||||
return new InvoiceEntity()
|
return new InvoiceEntity()
|
||||||
{
|
{
|
||||||
|
Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)),
|
||||||
|
StoreId = storeId,
|
||||||
Networks = _btcPayNetworkProvider,
|
Networks = _btcPayNetworkProvider,
|
||||||
Version = InvoiceEntity.Lastest_Version,
|
Version = InvoiceEntity.Lastest_Version,
|
||||||
InvoiceTime = DateTimeOffset.UtcNow,
|
// Truncating was an unintended side effect of previous code. Might want to remove that one day
|
||||||
Metadata = new InvoiceMetadata()
|
InvoiceTime = DateTimeOffset.UtcNow.TruncateMilliSeconds(),
|
||||||
|
Metadata = new InvoiceMetadata(),
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
Payments = new List<PaymentEntity>()
|
||||||
|
#pragma warning restore CS0618
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,21 +175,14 @@ namespace BTCPayServer.Services.Invoices
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<InvoiceEntity> CreateInvoiceAsync(string storeId, InvoiceEntity invoice, string[] additionalSearchTerms = null)
|
public async Task CreateInvoiceAsync(InvoiceEntity invoice, string[] additionalSearchTerms = null)
|
||||||
{
|
{
|
||||||
var textSearch = new HashSet<string>();
|
var textSearch = new HashSet<string>();
|
||||||
invoice = Clone(invoice);
|
|
||||||
invoice.Networks = _btcPayNetworkProvider;
|
|
||||||
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
|
||||||
#pragma warning disable CS0618
|
|
||||||
invoice.Payments = new List<PaymentEntity>();
|
|
||||||
#pragma warning restore CS0618
|
|
||||||
invoice.StoreId = storeId;
|
|
||||||
using (var context = _applicationDbContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = new Data.InvoiceData()
|
var invoiceData = new Data.InvoiceData()
|
||||||
{
|
{
|
||||||
StoreDataId = storeId,
|
StoreDataId = invoice.StoreId,
|
||||||
Id = invoice.Id,
|
Id = invoice.Id,
|
||||||
Created = invoice.InvoiceTime,
|
Created = invoice.InvoiceTime,
|
||||||
OrderId = invoice.Metadata.OrderId,
|
OrderId = invoice.Metadata.OrderId,
|
||||||
|
@ -245,16 +240,6 @@ namespace BTCPayServer.Services.Invoices
|
||||||
|
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return invoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
private InvoiceEntity Clone(InvoiceEntity invoice)
|
|
||||||
{
|
|
||||||
var temp = new InvoiceData();
|
|
||||||
temp.SetBlob(invoice);
|
|
||||||
return temp.GetBlob(_btcPayNetworkProvider);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddInvoiceLogs(string invoiceId, InvoiceLogs logs)
|
public async Task AddInvoiceLogs(string invoiceId, InvoiceLogs logs)
|
||||||
|
@ -617,6 +602,7 @@ namespace BTCPayServer.Services.Invoices
|
||||||
entity.Metadata.BuyerEmail = entity.RefundMail;
|
entity.Metadata.BuyerEmail = entity.RefundMail;
|
||||||
}
|
}
|
||||||
entity.Archived = invoice.Archived;
|
entity.Archived = invoice.Archived;
|
||||||
|
entity.UpdateTotals();
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -828,9 +814,8 @@ namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
var paymentMethodContribution = new InvoiceStatistics.Contribution();
|
var paymentMethodContribution = new InvoiceStatistics.Contribution();
|
||||||
paymentMethodContribution.PaymentMethodId = pay.GetPaymentMethodId();
|
paymentMethodContribution.PaymentMethodId = pay.GetPaymentMethodId();
|
||||||
paymentMethodContribution.Value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee;
|
paymentMethodContribution.CurrencyValue = pay.InvoicePaidAmount.Net;
|
||||||
var rate = p.GetPaymentMethod(paymentMethodContribution.PaymentMethodId).Rate;
|
paymentMethodContribution.Value = pay.PaidAmount.Net;
|
||||||
paymentMethodContribution.CurrencyValue = rate * paymentMethodContribution.Value;
|
|
||||||
return paymentMethodContribution;
|
return paymentMethodContribution;
|
||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
|
@ -51,7 +51,7 @@ namespace BTCPayServer.Services.Invoices
|
||||||
{
|
{
|
||||||
Version = 1,
|
Version = 1,
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
CryptoCode = network.CryptoCode,
|
Currency = network.CryptoCode,
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
ReceivedTime = date.UtcDateTime,
|
ReceivedTime = date.UtcDateTime,
|
||||||
Accounted = accounted,
|
Accounted = accounted,
|
||||||
|
|
Loading…
Add table
Reference in a new issue