From a05cd5678b941dffa2f58e1a5502ab6ccdf7be26 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sat, 5 Jan 2019 00:37:09 +0900 Subject: [PATCH] Add support for removing network fee on first payment --- BTCPayServer.Tests/BTCPayServerTester.cs | 1 + BTCPayServer.Tests/ServerTester.cs | 2 + BTCPayServer.Tests/TestAccount.cs | 9 ++ BTCPayServer.Tests/UnitTest1.cs | 108 ++++++++++++++++-- BTCPayServer/Controllers/InvoiceController.cs | 2 - BTCPayServer/Controllers/StoresController.cs | 4 +- BTCPayServer/Data/StoreData.cs | 19 ++- .../HostedServices/MigratorHostedService.cs | 26 +++++ .../Models/StoreViewModels/StoreViewModel.cs | 4 +- .../BitcoinLikeOnChainPaymentMethod.cs | 8 +- .../Bitcoin/BitcoinLikePaymentHandler.cs | 12 +- .../Payments/Bitcoin/NBXplorerListener.cs | 4 +- .../Payments/IPaymentMethodDetails.cs | 1 - .../LightningLikePaymentMethodDetails.cs | 6 +- .../Payments/Lightning/LightningListener.cs | 2 +- .../Services/Invoices/InvoiceRepository.cs | 26 +++-- BTCPayServer/Services/MigrationSettings.cs | 1 + BTCPayServer/Views/Stores/UpdateStore.cshtml | 8 +- 18 files changed, 199 insertions(+), 44 deletions(-) diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index a369bec67..956defe20 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -34,6 +34,7 @@ using System.Security.Principal; using System.Text; using System.Threading; using Xunit; +using BTCPayServer.Services; namespace BTCPayServer.Tests { diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index 2129776cd..db8e71914 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -22,6 +22,7 @@ using BTCPayServer.Tests.Lnd; using BTCPayServer.Payments.Lightning; using BTCPayServer.Lightning.CLightning; using BTCPayServer.Lightning; +using BTCPayServer.Services; namespace BTCPayServer.Tests { @@ -152,6 +153,7 @@ namespace BTCPayServer.Tests { get; set; } + public List Stores { get; internal set; } = new List(); public void Dispose() diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index 5669a8818..16c91bd2b 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -17,6 +17,7 @@ using BTCPayServer.Payments.Lightning; using BTCPayServer.Tests.Logging; using BTCPayServer.Lightning; using BTCPayServer.Lightning.CLightning; +using BTCPayServer.Data; namespace BTCPayServer.Tests { @@ -58,6 +59,14 @@ namespace BTCPayServer.Tests CreateStoreAsync().GetAwaiter().GetResult(); } + public void SetNetworkFeeMode(NetworkFeeMode mode) + { + var storeController = GetController(); + StoreViewModel store = (StoreViewModel)((ViewResult)storeController.UpdateStore()).Model; + store.NetworkFeeMode = mode; + storeController.UpdateStore(store).GetAwaiter().GetResult(); + } + public T GetController(bool setImplicitStore = true) where T : Controller { return parent.PayTester.GetController(UserId, setImplicitStore ? StoreId : null); diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index f51d34d96..12f1ad5f7 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -49,6 +49,8 @@ using BTCPayServer.Security; using NBXplorer.Models; using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel; using NBitpayClient.Extensions; +using BTCPayServer.Services; +using System.Text.RegularExpressions; namespace BTCPayServer.Tests { @@ -349,7 +351,7 @@ namespace BTCPayServer.Tests (1000.0001m, "₹ 1,000.00 (INR)", "INR") }) { - var actual = new CurrencyNameTable().DisplayFormatCurrency(test.Item1, test.Item3); + var actual = new CurrencyNameTable().DisplayFormatCurrency(test.Item1, test.Item3); actual = actual.Replace("¥", "¥"); // Hack so JPY test pass on linux as well Assert.Equal(test.Item2, actual); } @@ -494,6 +496,7 @@ namespace BTCPayServer.Tests var acc = tester.NewAccount(); acc.GrantAccess(); acc.RegisterDerivationScheme("BTC"); + acc.SetNetworkFeeMode(NetworkFeeMode.Always); var invoice = acc.BitPay.CreateInvoice(new Invoice() { Price = 5.0m, @@ -726,6 +729,7 @@ namespace BTCPayServer.Tests var user = tester.NewAccount(); user.GrantAccess(); user.RegisterDerivationScheme("BTC"); + user.SetNetworkFeeMode(NetworkFeeMode.Always); var invoice = user.BitPay.CreateInvoice(new Invoice() { Price = 5000.0m, @@ -1591,13 +1595,19 @@ donation: [Trait("Integration", "Integration")] public void CanExportInvoicesJson() { + decimal GetFieldValue(string input, string fieldName) + { + var match = Regex.Match(input, $"\"{fieldName}\":([^,]*)"); + Assert.True(match.Success); + return decimal.Parse(match.Groups[1].Value.Trim(), CultureInfo.InvariantCulture); + } using (var tester = ServerTester.Create()) { tester.Start(); var user = tester.NewAccount(); user.GrantAccess(); user.RegisterDerivationScheme("BTC"); - + user.SetNetworkFeeMode(NetworkFeeMode.Always); var invoice = user.BitPay.CreateInvoice(new Invoice() { Price = 10, @@ -1608,8 +1618,7 @@ donation: FullNotifications = true }, Facade.Merchant); - var networkFee = Money.Satoshis(10000); - + var networkFee = new FeeRate(invoice.MinerFees["BTC"].SatoshiPerBytes).GetFee(100); // ensure 0 invoices exported because there are no payments yet var jsonResult = user.GetController().Export("json").GetAwaiter().GetResult(); var result = Assert.IsType(jsonResult); @@ -1619,7 +1628,7 @@ donation: var cashCow = tester.ExplorerNode; var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); // - var firstPayment = invoice.CryptoInfo[0].TotalDue - 3*networkFee; + var firstPayment = invoice.CryptoInfo[0].TotalDue - 3 * networkFee; cashCow.SendToAddress(invoiceAddress, firstPayment); Thread.Sleep(1000); // prevent race conditions, ordering payments // look if you can reduce thread sleep, this was min value for me @@ -1629,7 +1638,7 @@ donation: Thread.Sleep(1000); // pay remaining amount - cashCow.SendToAddress(invoiceAddress, 4*networkFee); + cashCow.SendToAddress(invoiceAddress, 4 * networkFee); Thread.Sleep(1000); Eventually(() => @@ -1641,21 +1650,102 @@ donation: var parsedJson = JsonConvert.DeserializeObject(paidresult.Content); Assert.Equal(3, parsedJson.Length); + var invoiceDueAfterFirstPayment = (3 * networkFee).ToDecimal(MoneyUnit.BTC) * invoice.Rate; var pay1str = parsedJson[0].ToString(); Assert.Contains("\"InvoiceItemDesc\": \"Some \\\", description\"", pay1str); - Assert.Contains("\"InvoiceDue\": 1.5", pay1str); + Assert.Equal(invoiceDueAfterFirstPayment, GetFieldValue(pay1str, "InvoiceDue")); Assert.Contains("\"InvoicePrice\": 10.0", pay1str); Assert.Contains("\"ConversionRate\": 5000.0", pay1str); Assert.Contains($"\"InvoiceId\": \"{invoice.Id}\",", pay1str); var pay2str = parsedJson[1].ToString(); - Assert.Contains("\"InvoiceDue\": 1.5", pay2str); + Assert.Equal(invoiceDueAfterFirstPayment, GetFieldValue(pay2str, "InvoiceDue")); var pay3str = parsedJson[2].ToString(); Assert.Contains("\"InvoiceDue\": 0", pay3str); }); } } + [Fact] + [Trait("Integration", "Integration")] + public void CanChangeNetworkFeeMode() + { + using (var tester = ServerTester.Create()) + { + tester.Start(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterDerivationScheme("BTC"); + foreach (var networkFeeMode in Enum.GetValues(typeof(NetworkFeeMode)).Cast()) + { + Logs.Tester.LogInformation($"Trying with {nameof(networkFeeMode)}={networkFeeMode}"); + user.SetNetworkFeeMode(networkFeeMode); + var invoice = user.BitPay.CreateInvoice(new Invoice() + { + Price = 10, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some \", description", + FullNotifications = true + }, Facade.Merchant); + + var networkFee = Money.Satoshis(10000).ToDecimal(MoneyUnit.BTC); + var missingMoney = Money.Satoshis(5000).ToDecimal(MoneyUnit.BTC); + var cashCow = tester.ExplorerNode; + var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); + + // Check that for the first payment, no network fee are included + var due = Money.Parse(invoice.CryptoInfo[0].Due); + var productPartDue = (invoice.Price / invoice.Rate); + switch (networkFeeMode) + { + case NetworkFeeMode.MultiplePaymentsOnly: + case NetworkFeeMode.Never: + Assert.Equal(productPartDue, due.ToDecimal(MoneyUnit.BTC)); + break; + case NetworkFeeMode.Always: + Assert.Equal(productPartDue + networkFee, due.ToDecimal(MoneyUnit.BTC)); + break; + default: + throw new NotSupportedException(networkFeeMode.ToString()); + } + var firstPayment = productPartDue - missingMoney; + cashCow.SendToAddress(invoiceAddress, Money.Coins(firstPayment)); + + Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + // Check that for the second payment, network fee are included + due = Money.Parse(invoice.CryptoInfo[0].Due); + Assert.Equal(Money.Coins(firstPayment), Money.Parse(invoice.CryptoInfo[0].Paid)); + switch (networkFeeMode) + { + case NetworkFeeMode.MultiplePaymentsOnly: + Assert.Equal(missingMoney + networkFee, due.ToDecimal(MoneyUnit.BTC)); + Assert.Equal(firstPayment + missingMoney + networkFee, Money.Parse(invoice.CryptoInfo[0].TotalDue).ToDecimal(MoneyUnit.BTC)); + break; + case NetworkFeeMode.Always: + Assert.Equal(missingMoney + 2 * networkFee, due.ToDecimal(MoneyUnit.BTC)); + Assert.Equal(firstPayment + missingMoney + 2 * networkFee, Money.Parse(invoice.CryptoInfo[0].TotalDue).ToDecimal(MoneyUnit.BTC)); + break; + case NetworkFeeMode.Never: + Assert.Equal(missingMoney, due.ToDecimal(MoneyUnit.BTC)); + Assert.Equal(firstPayment + missingMoney, Money.Parse(invoice.CryptoInfo[0].TotalDue).ToDecimal(MoneyUnit.BTC)); + break; + default: + throw new NotSupportedException(networkFeeMode.ToString()); + } + }); + cashCow.SendToAddress(invoiceAddress, due); + Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.Equal("paid", invoice.Status); + }); + } + } + } [Fact] [Trait("Integration", "Integration")] @@ -1667,7 +1757,7 @@ donation: var user = tester.NewAccount(); user.GrantAccess(); user.RegisterDerivationScheme("BTC"); - + user.SetNetworkFeeMode(NetworkFeeMode.Always); var invoice = user.BitPay.CreateInvoice(new Invoice() { Price = 500, diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index 9ff5c468a..de58dc171 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -200,8 +200,6 @@ namespace BTCPayServer.Controllers paymentMethod.SetId(supportedPaymentMethod.PaymentId); paymentMethod.Rate = rate.BidAsk.Bid; var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, store, network, preparePayment); - if (storeBlob.NetworkFeeDisabled) - paymentDetails.SetNoNetworkFee(); paymentMethod.SetPaymentMethodDetails(paymentDetails); Func compare = null; diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 00e812ea0..23aa50234 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -406,7 +406,7 @@ namespace BTCPayServer.Controllers vm.Id = store.Id; vm.StoreName = store.StoreName; vm.StoreWebsite = store.StoreWebsite; - vm.NetworkFee = !storeBlob.NetworkFeeDisabled; + vm.NetworkFeeMode = storeBlob.NetworkFeeMode; vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice; vm.SpeedPolicy = store.SpeedPolicy; vm.CanDelete = _Repo.CanDeleteStores(); @@ -489,7 +489,7 @@ namespace BTCPayServer.Controllers var blob = StoreData.GetStoreBlob(); blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice; - blob.NetworkFeeDisabled = !model.NetworkFee; + blob.NetworkFeeMode = model.NetworkFeeMode; blob.MonitoringExpiration = model.MonitoringExpiration; blob.InvoiceExpiration = model.InvoiceExpiration; blob.LightningDescriptionTemplate = model.LightningDescriptionTemplate ?? string.Empty; diff --git a/BTCPayServer/Data/StoreData.cs b/BTCPayServer/Data/StoreData.cs index a45c34111..29347ed7a 100644 --- a/BTCPayServer/Data/StoreData.cs +++ b/BTCPayServer/Data/StoreData.cs @@ -249,6 +249,12 @@ namespace BTCPayServer.Data } } + public enum NetworkFeeMode + { + MultiplePaymentsOnly, + Always, + Never + } public class StoreBlob { public StoreBlob() @@ -258,10 +264,21 @@ namespace BTCPayServer.Data PaymentTolerance = 0; RequiresRefundEmail = true; } - public bool NetworkFeeDisabled + + [Obsolete("Use NetworkFeeMode instead")] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool? NetworkFeeDisabled { get; set; } + + [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public NetworkFeeMode NetworkFeeMode + { + get; + set; + } + public bool RequiresRefundEmail { get; set; } public string DefaultLang { get; set; } diff --git a/BTCPayServer/HostedServices/MigratorHostedService.cs b/BTCPayServer/HostedServices/MigratorHostedService.cs index 6861a51a7..8afcc70c3 100644 --- a/BTCPayServer/HostedServices/MigratorHostedService.cs +++ b/BTCPayServer/HostedServices/MigratorHostedService.cs @@ -59,6 +59,12 @@ namespace BTCPayServer.HostedServices settings.ConvertMultiplierToSpread = true; await _Settings.UpdateSetting(settings); } + if (!settings.ConvertNetworkFeeProperty) + { + await ConvertNetworkFeeProperty(); + settings.ConvertNetworkFeeProperty = true; + await _Settings.UpdateSetting(settings); + } } catch (Exception ex) { @@ -67,6 +73,26 @@ namespace BTCPayServer.HostedServices } } + private async Task ConvertNetworkFeeProperty() + { + using (var ctx = _DBContextFactory.CreateContext()) + { + foreach (var store in await ctx.Stores.ToArrayAsync()) + { + var blob = store.GetStoreBlob(); +#pragma warning disable CS0618 // Type or member is obsolete + if (blob.NetworkFeeDisabled != null) + { + blob.NetworkFeeMode = blob.NetworkFeeDisabled.Value ? NetworkFeeMode.Never : NetworkFeeMode.Always; + blob.NetworkFeeDisabled = null; + store.SetStoreBlob(blob); + } +#pragma warning restore CS0618 // Type or member is obsolete + } + await ctx.SaveChangesAsync(); + } + } + private async Task ConvertMultiplierToSpread() { using (var ctx = _DBContextFactory.CreateContext()) diff --git a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs index 77219cc03..b6c55b6a4 100644 --- a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs @@ -82,8 +82,8 @@ namespace BTCPayServer.Models.StoreViewModels get; set; } - [Display(Name = "Add network fee to invoice (vary with mining fees)")] - public bool NetworkFee + [Display(Name = "Add additional fee (network fee) to invoice...")] + public Data.NetworkFeeMode NetworkFeeMode { get; set; } diff --git a/BTCPayServer/Payments/Bitcoin/BitcoinLikeOnChainPaymentMethod.cs b/BTCPayServer/Payments/Bitcoin/BitcoinLikeOnChainPaymentMethod.cs index 5034df989..4cc0d91af 100644 --- a/BTCPayServer/Payments/Bitcoin/BitcoinLikeOnChainPaymentMethod.cs +++ b/BTCPayServer/Payments/Bitcoin/BitcoinLikeOnChainPaymentMethod.cs @@ -24,17 +24,11 @@ namespace BTCPayServer.Payments.Bitcoin { return NetworkFee.ToDecimal(MoneyUnit.BTC); } - - public void SetNoNetworkFee() - { - NetworkFee = Money.Zero; - } - - public void SetPaymentDestination(string newPaymentDestination) { DepositAddress = newPaymentDestination; } + public Data.NetworkFeeMode NetworkFeeMode { get; set; } // Those properties are JsonIgnore because their data is inside CryptoData class for legacy reason [JsonIgnore] diff --git a/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs b/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs index a7e075921..758fdc5fa 100644 --- a/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs +++ b/BTCPayServer/Payments/Bitcoin/BitcoinLikePaymentHandler.cs @@ -48,8 +48,18 @@ namespace BTCPayServer.Payments.Bitcoin throw new PaymentMethodUnavailableException($"Full node not available"); var prepare = (Prepare)preparePaymentObject; Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod onchainMethod = new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod(); + onchainMethod.NetworkFeeMode = store.GetStoreBlob().NetworkFeeMode; onchainMethod.FeeRate = await prepare.GetFeeRate; - onchainMethod.NetworkFee = onchainMethod.FeeRate.GetFee(100); // assume price for 100 bytes + switch (onchainMethod.NetworkFeeMode) + { + case NetworkFeeMode.Always: + onchainMethod.NetworkFee = onchainMethod.FeeRate.GetFee(100); // assume price for 100 bytes + break; + case NetworkFeeMode.Never: + case NetworkFeeMode.MultiplePaymentsOnly: + onchainMethod.NetworkFee = Money.Zero; + break; + } onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString(); return onchainMethod; } diff --git a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs index ca0860629..ad8acabc4 100644 --- a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs +++ b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs @@ -158,7 +158,7 @@ namespace BTCPayServer.Payments.Bitcoin var alreadyExist = GetAllBitcoinPaymentData(invoice).Where(c => c.GetPaymentId() == paymentData.GetPaymentId()).Any(); if (!alreadyExist) { - var payment = await _InvoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, paymentData, network.CryptoCode); + var payment = await _InvoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, paymentData, network); if(payment != null) await ReceivedPayment(wallet, invoice, payment, evt.DerivationStrategy); } @@ -332,7 +332,7 @@ namespace BTCPayServer.Payments.Bitcoin { var transaction = await wallet.GetTransactionAsync(coin.Coin.Outpoint.Hash); var paymentData = new BitcoinLikePaymentData(coin.Coin, transaction.Transaction.RBF); - var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.Timestamp, paymentData, network.CryptoCode).ConfigureAwait(false); + var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.Timestamp, paymentData, network).ConfigureAwait(false); alreadyAccounted.Add(coin.Coin.Outpoint); if (payment != null) { diff --git a/BTCPayServer/Payments/IPaymentMethodDetails.cs b/BTCPayServer/Payments/IPaymentMethodDetails.cs index b8a260bbe..b6d472b1e 100644 --- a/BTCPayServer/Payments/IPaymentMethodDetails.cs +++ b/BTCPayServer/Payments/IPaymentMethodDetails.cs @@ -22,7 +22,6 @@ namespace BTCPayServer.Payments /// /// decimal GetNetworkFee(); - void SetNoNetworkFee(); /// /// Change the payment destination (internal plumbing) /// diff --git a/BTCPayServer/Payments/Lightning/LightningLikePaymentMethodDetails.cs b/BTCPayServer/Payments/Lightning/LightningLikePaymentMethodDetails.cs index 0b3d08a6f..a396e5ca3 100644 --- a/BTCPayServer/Payments/Lightning/LightningLikePaymentMethodDetails.cs +++ b/BTCPayServer/Payments/Lightning/LightningLikePaymentMethodDetails.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NBitcoin; namespace BTCPayServer.Payments.Lightning { @@ -25,11 +26,6 @@ namespace BTCPayServer.Payments.Lightning { return 0.0m; } - - public void SetNoNetworkFee() - { - } - public void SetPaymentDestination(string newPaymentDestination) { BOLT11 = newPaymentDestination; diff --git a/BTCPayServer/Payments/Lightning/LightningListener.cs b/BTCPayServer/Payments/Lightning/LightningListener.cs index abf56011b..e81e14400 100644 --- a/BTCPayServer/Payments/Lightning/LightningListener.cs +++ b/BTCPayServer/Payments/Lightning/LightningListener.cs @@ -192,7 +192,7 @@ namespace BTCPayServer.Payments.Lightning { BOLT11 = notification.BOLT11, Amount = notification.Amount - }, network.CryptoCode, accounted: true); + }, network, accounted: true); if (payment != null) { var invoice = await _InvoiceRepository.GetInvoice(listenedInvoice.InvoiceId); diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index 7e18bbcb2..1ae4befc7 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -193,7 +193,7 @@ retry: return paymentMethod.GetPaymentMethodDetails().GetPaymentDestination(); } - public async Task NewAddress(string invoiceId, IPaymentMethodDetails paymentMethod, BTCPayNetwork network) + public async Task NewAddress(string invoiceId, Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod paymentMethod, BTCPayNetwork network) { using (var context = _ContextFactory.CreateContext()) { @@ -206,14 +206,13 @@ retry: if (currencyData == null) return false; - var existingPaymentMethod = currencyData.GetPaymentMethodDetails(); + var existingPaymentMethod = (Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod)currencyData.GetPaymentMethodDetails(); if (existingPaymentMethod.GetPaymentDestination() != null) { MarkUnassigned(invoiceId, invoiceEntity, context, currencyData.GetId()); } existingPaymentMethod.SetPaymentDestination(paymentMethod.GetPaymentDestination()); - currencyData.SetPaymentMethodDetails(existingPaymentMethod); #pragma warning disable CS0618 if (network.IsBTC) @@ -560,28 +559,37 @@ retry: /// /// /// The PaymentEntity or null if already added - public async Task AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, string cryptoCode, bool accounted = false) + public async Task AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, BTCPayNetwork network, bool accounted = false) { using (var context = _ContextFactory.CreateContext()) { var invoice = context.Invoices.Find(invoiceId); if (invoice == null) return null; + InvoiceEntity invoiceEntity = ToObject(invoice.Blob, network.NBitcoinNetwork); + PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()), null); + IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails(); PaymentEntity entity = new PaymentEntity { Version = 1, #pragma warning disable CS0618 - CryptoCode = cryptoCode, + CryptoCode = network.CryptoCode, #pragma warning restore CS0618 ReceivedTime = date.UtcDateTime, Accounted = accounted, - NetworkFee = ToObject(invoice.Blob, null) - .GetPaymentMethod(new PaymentMethodId(cryptoCode, paymentData.GetPaymentType()), null) - .GetPaymentMethodDetails().GetNetworkFee() + NetworkFee = paymentMethodDetails.GetNetworkFee() }; entity.SetCryptoPaymentData(paymentData); - + if (paymentMethodDetails is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod bitcoinPaymentMethod && + bitcoinPaymentMethod.NetworkFeeMode == NetworkFeeMode.MultiplePaymentsOnly && + bitcoinPaymentMethod.NetworkFee == Money.Zero) + { + bitcoinPaymentMethod.NetworkFee = bitcoinPaymentMethod.FeeRate.GetFee(100); // assume price for 100 bytes + paymentMethod.SetPaymentMethodDetails(bitcoinPaymentMethod); + invoiceEntity.SetPaymentMethod(paymentMethod); + invoice.Blob = ToBytes(invoiceEntity, network.NBitcoinNetwork); + } PaymentData data = new PaymentData { Id = paymentData.GetPaymentId(), diff --git a/BTCPayServer/Services/MigrationSettings.cs b/BTCPayServer/Services/MigrationSettings.cs index 93486d06e..1d0d7ccc5 100644 --- a/BTCPayServer/Services/MigrationSettings.cs +++ b/BTCPayServer/Services/MigrationSettings.cs @@ -10,5 +10,6 @@ namespace BTCPayServer.Services public bool UnreachableStoreCheck { get; set; } public bool DeprecatedLightningConnectionStringCheck { get; set; } public bool ConvertMultiplierToSpread { get; set; } + public bool ConvertNetworkFeeProperty { get; set; } } } diff --git a/BTCPayServer/Views/Stores/UpdateStore.cshtml b/BTCPayServer/Views/Stores/UpdateStore.cshtml index 6e53340f1..ea8fa5cd7 100644 --- a/BTCPayServer/Views/Stores/UpdateStore.cshtml +++ b/BTCPayServer/Views/Stores/UpdateStore.cshtml @@ -43,9 +43,13 @@
- + - +