mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Merge branch 'master' into feature/crowdfund
This commit is contained in:
@@ -34,6 +34,7 @@ using System.Security.Principal;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using BTCPayServer.Services;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,14 +8,18 @@ using Microsoft.AspNetCore.Builder;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
public class CustomServer : IDisposable
|
public class CustomServer : IDisposable
|
||||||
{
|
{
|
||||||
TaskCompletionSource<bool> _Evt = null;
|
|
||||||
IWebHost _Host = null;
|
IWebHost _Host = null;
|
||||||
CancellationTokenSource _Closed = new CancellationTokenSource();
|
CancellationTokenSource _Closed = new CancellationTokenSource();
|
||||||
|
Channel<JObject> _Requests = Channel.CreateUnbounded<JObject>();
|
||||||
public CustomServer()
|
public CustomServer()
|
||||||
{
|
{
|
||||||
var port = Utils.FreeTcpPort();
|
var port = Utils.FreeTcpPort();
|
||||||
@@ -24,14 +28,7 @@ namespace BTCPayServer.Tests
|
|||||||
{
|
{
|
||||||
app.Run(req =>
|
app.Run(req =>
|
||||||
{
|
{
|
||||||
while (_Act == null)
|
_Requests.Writer.WriteAsync(JsonConvert.DeserializeObject<JObject>(new StreamReader(req.Request.Body).ReadToEnd()), _Closed.Token);
|
||||||
{
|
|
||||||
Thread.Sleep(10);
|
|
||||||
_Closed.Token.ThrowIfCancellationRequested();
|
|
||||||
}
|
|
||||||
_Act(req);
|
|
||||||
_Act = null;
|
|
||||||
_Evt.TrySetResult(true);
|
|
||||||
req.Response.StatusCode = 200;
|
req.Response.StatusCode = 200;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
});
|
});
|
||||||
@@ -47,22 +44,24 @@ namespace BTCPayServer.Tests
|
|||||||
return new Uri(_Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First());
|
return new Uri(_Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
Action<HttpContext> _Act;
|
public async Task<JObject> GetNextRequest()
|
||||||
public void ProcessNextRequest(Action<HttpContext> act)
|
|
||||||
{
|
{
|
||||||
var source = new TaskCompletionSource<bool>();
|
using (CancellationTokenSource cancellation = new CancellationTokenSource(2000000))
|
||||||
CancellationTokenSource cancellation = new CancellationTokenSource(20000);
|
|
||||||
cancellation.Token.Register(() => source.TrySetCanceled());
|
|
||||||
source = new TaskCompletionSource<bool>();
|
|
||||||
_Evt = source;
|
|
||||||
_Act = act;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
_Evt.Task.GetAwaiter().GetResult();
|
try
|
||||||
}
|
{
|
||||||
catch (TaskCanceledException)
|
JObject req = null;
|
||||||
{
|
while(!await _Requests.Reader.WaitToReadAsync(cancellation.Token) ||
|
||||||
throw new Xunit.Sdk.XunitException("Callback to the webserver was expected, check if the callback url is accessible from internet");
|
!_Requests.Reader.TryRead(out req))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
throw new Xunit.Sdk.XunitException("Callback to the webserver was expected, check if the callback url is accessible from internet");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ using BTCPayServer.Tests.Lnd;
|
|||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Lightning.CLightning;
|
using BTCPayServer.Lightning.CLightning;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
|
using BTCPayServer.Services;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
@@ -152,6 +153,7 @@ namespace BTCPayServer.Tests
|
|||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<string> Stores { get; internal set; } = new List<string>();
|
public List<string> Stores { get; internal set; } = new List<string>();
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ using BTCPayServer.Payments.Lightning;
|
|||||||
using BTCPayServer.Tests.Logging;
|
using BTCPayServer.Tests.Logging;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Lightning.CLightning;
|
using BTCPayServer.Lightning.CLightning;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
@@ -58,6 +59,21 @@ namespace BTCPayServer.Tests
|
|||||||
CreateStoreAsync().GetAwaiter().GetResult();
|
CreateStoreAsync().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetNetworkFeeMode(NetworkFeeMode mode)
|
||||||
|
{
|
||||||
|
ModifyStore((store) =>
|
||||||
|
{
|
||||||
|
store.NetworkFeeMode = mode;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public void ModifyStore(Action<StoreViewModel> modify)
|
||||||
|
{
|
||||||
|
var storeController = GetController<StoresController>();
|
||||||
|
StoreViewModel store = (StoreViewModel)((ViewResult)storeController.UpdateStore()).Model;
|
||||||
|
modify(store);
|
||||||
|
storeController.UpdateStore(store).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
public T GetController<T>(bool setImplicitStore = true) where T : Controller
|
public T GetController<T>(bool setImplicitStore = true) where T : Controller
|
||||||
{
|
{
|
||||||
return parent.PayTester.GetController<T>(UserId, setImplicitStore ? StoreId : null);
|
return parent.PayTester.GetController<T>(UserId, setImplicitStore ? StoreId : null);
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ using BTCPayServer.Security;
|
|||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
|
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
|
||||||
using NBitpayClient.Extensions;
|
using NBitpayClient.Extensions;
|
||||||
|
using BTCPayServer.Services;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
@@ -98,7 +100,7 @@ namespace BTCPayServer.Tests
|
|||||||
Rate = 10513.44m,
|
Rate = 10513.44m,
|
||||||
}.SetPaymentMethodDetails(new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
}.SetPaymentMethodDetails(new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
||||||
{
|
{
|
||||||
TxFee = Money.Coins(0.00000100m),
|
NetworkFee = Money.Coins(0.00000100m),
|
||||||
DepositAddress = dummy
|
DepositAddress = dummy
|
||||||
}));
|
}));
|
||||||
paymentMethods.Add(new PaymentMethod()
|
paymentMethods.Add(new PaymentMethod()
|
||||||
@@ -107,7 +109,7 @@ namespace BTCPayServer.Tests
|
|||||||
Rate = 216.79m
|
Rate = 216.79m
|
||||||
}.SetPaymentMethodDetails(new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
}.SetPaymentMethodDetails(new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod()
|
||||||
{
|
{
|
||||||
TxFee = Money.Coins(0.00010000m),
|
NetworkFee = Money.Coins(0.00010000m),
|
||||||
DepositAddress = dummy
|
DepositAddress = dummy
|
||||||
}));
|
}));
|
||||||
invoiceEntity.SetPaymentMethods(paymentMethods);
|
invoiceEntity.SetPaymentMethods(paymentMethods);
|
||||||
@@ -115,12 +117,12 @@ namespace BTCPayServer.Tests
|
|||||||
var btc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
var btc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
||||||
var accounting = btc.Calculate();
|
var accounting = btc.Calculate();
|
||||||
|
|
||||||
invoiceEntity.Payments.Add(new PaymentEntity() { Accounted = true, CryptoCode = "BTC" }.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
invoiceEntity.Payments.Add(new PaymentEntity() { Accounted = true, CryptoCode = "BTC", NetworkFee = 0.00000100m }.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
||||||
{
|
{
|
||||||
Output = new TxOut() { Value = Money.Coins(0.00151263m) }
|
Output = new TxOut() { Value = Money.Coins(0.00151263m) }
|
||||||
}));
|
}));
|
||||||
accounting = btc.Calculate();
|
accounting = btc.Calculate();
|
||||||
invoiceEntity.Payments.Add(new PaymentEntity() { Accounted = true, CryptoCode = "BTC" }.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
invoiceEntity.Payments.Add(new PaymentEntity() { Accounted = true, CryptoCode = "BTC", NetworkFee = 0.00000100m }.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
||||||
{
|
{
|
||||||
Output = new TxOut() { Value = accounting.Due }
|
Output = new TxOut() { Value = accounting.Due }
|
||||||
}));
|
}));
|
||||||
@@ -147,7 +149,7 @@ namespace BTCPayServer.Tests
|
|||||||
var entity = new InvoiceEntity();
|
var entity = new InvoiceEntity();
|
||||||
#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() { CryptoCode = "BTC", Rate = 5000, TxFee = Money.Coins(0.1m) });
|
entity.SetPaymentMethod(new PaymentMethod() { CryptoCode = "BTC", Rate = 5000, NetworkFee = Money.Coins(0.1m) });
|
||||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||||
|
|
||||||
var paymentMethod = entity.GetPaymentMethods(null).TryGet("BTC", PaymentTypes.BTCLike);
|
var paymentMethod = entity.GetPaymentMethods(null).TryGet("BTC", PaymentTypes.BTCLike);
|
||||||
@@ -155,20 +157,20 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Equal(Money.Coins(1.1m), accounting.Due);
|
Assert.Equal(Money.Coins(1.1m), accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
|
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()), Accounted = true });
|
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.5m), new Key()), Accounted = true, NetworkFee = 0.1m });
|
||||||
|
|
||||||
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(Money.Coins(0.7m), accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.2m), accounting.TotalDue);
|
Assert.Equal(Money.Coins(1.2m), accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true });
|
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.2m), new Key()), Accounted = true, NetworkFee = 0.1m });
|
||||||
|
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Coins(0.6m), accounting.Due);
|
Assert.Equal(Money.Coins(0.6m), accounting.Due);
|
||||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()), Accounted = true });
|
entity.Payments.Add(new PaymentEntity() { Output = new TxOut(Money.Coins(0.6m), new Key()), Accounted = true, NetworkFee = 0.1m });
|
||||||
|
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Zero, accounting.Due);
|
Assert.Equal(Money.Zero, accounting.Due);
|
||||||
@@ -187,13 +189,13 @@ namespace BTCPayServer.Tests
|
|||||||
{
|
{
|
||||||
CryptoCode = "BTC",
|
CryptoCode = "BTC",
|
||||||
Rate = 1000,
|
Rate = 1000,
|
||||||
TxFee = Money.Coins(0.1m)
|
NetworkFee = Money.Coins(0.1m)
|
||||||
});
|
});
|
||||||
paymentMethods.Add(new PaymentMethod()
|
paymentMethods.Add(new PaymentMethod()
|
||||||
{
|
{
|
||||||
CryptoCode = "LTC",
|
CryptoCode = "LTC",
|
||||||
Rate = 500,
|
Rate = 500,
|
||||||
TxFee = Money.Coins(0.01m)
|
NetworkFee = Money.Coins(0.01m)
|
||||||
});
|
});
|
||||||
entity.SetPaymentMethods(paymentMethods);
|
entity.SetPaymentMethods(paymentMethods);
|
||||||
entity.Payments = new List<PaymentEntity>();
|
entity.Payments = new List<PaymentEntity>();
|
||||||
@@ -205,7 +207,7 @@ namespace BTCPayServer.Tests
|
|||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
Assert.Equal(Money.Coins(10.01m), accounting.TotalDue);
|
Assert.Equal(Money.Coins(10.01m), accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true });
|
entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true, NetworkFee = 0.1m });
|
||||||
|
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
@@ -222,7 +224,7 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Equal(Money.Coins(2.0m), accounting.Paid);
|
Assert.Equal(Money.Coins(2.0m), accounting.Paid);
|
||||||
Assert.Equal(Money.Coins(10.01m + 0.1m * 2), accounting.TotalDue);
|
Assert.Equal(Money.Coins(10.01m + 0.1m * 2), accounting.TotalDue);
|
||||||
|
|
||||||
entity.Payments.Add(new PaymentEntity() { CryptoCode = "LTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true });
|
entity.Payments.Add(new PaymentEntity() { CryptoCode = "LTC", Output = new TxOut(Money.Coins(1.0m), new Key()), Accounted = true, NetworkFee = 0.01m });
|
||||||
|
|
||||||
|
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
||||||
@@ -242,7 +244,7 @@ namespace BTCPayServer.Tests
|
|||||||
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);
|
||||||
entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(remaining, new Key()), Accounted = true });
|
entity.Payments.Add(new PaymentEntity() { CryptoCode = "BTC", Output = new TxOut(remaining, new Key()), Accounted = true, NetworkFee = 0.1m });
|
||||||
|
|
||||||
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
paymentMethod = entity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike), null);
|
||||||
accounting = paymentMethod.Calculate();
|
accounting = paymentMethod.Calculate();
|
||||||
@@ -272,7 +274,7 @@ namespace BTCPayServer.Tests
|
|||||||
var entity = new InvoiceEntity();
|
var entity = new InvoiceEntity();
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
entity.Payments = new List<PaymentEntity>();
|
entity.Payments = new List<PaymentEntity>();
|
||||||
entity.SetPaymentMethod(new PaymentMethod() { CryptoCode = "BTC", Rate = 5000, TxFee = Money.Coins(0.1m) });
|
entity.SetPaymentMethod(new PaymentMethod() { CryptoCode = "BTC", Rate = 5000, NetworkFee = Money.Coins(0.1m) });
|
||||||
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
entity.ProductInformation = new ProductInformation() { Price = 5000 };
|
||||||
entity.PaymentTolerance = 0;
|
entity.PaymentTolerance = 0;
|
||||||
|
|
||||||
@@ -484,7 +486,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Integration", "Integration")]
|
[Trait("Integration", "Integration")]
|
||||||
public void CanSendIPN()
|
public async Task CanSendIPN()
|
||||||
{
|
{
|
||||||
using (var callbackServer = new CustomServer())
|
using (var callbackServer = new CustomServer())
|
||||||
{
|
{
|
||||||
@@ -494,6 +496,7 @@ namespace BTCPayServer.Tests
|
|||||||
var acc = tester.NewAccount();
|
var acc = tester.NewAccount();
|
||||||
acc.GrantAccess();
|
acc.GrantAccess();
|
||||||
acc.RegisterDerivationScheme("BTC");
|
acc.RegisterDerivationScheme("BTC");
|
||||||
|
acc.ModifyStore(s => s.SpeedPolicy = SpeedPolicy.LowSpeed);
|
||||||
var invoice = acc.BitPay.CreateInvoice(new Invoice()
|
var invoice = acc.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Price = 5.0m,
|
Price = 5.0m,
|
||||||
@@ -506,13 +509,43 @@ namespace BTCPayServer.Tests
|
|||||||
ExtendedNotifications = true
|
ExtendedNotifications = true
|
||||||
});
|
});
|
||||||
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
|
||||||
tester.ExplorerNode.SendToAddress(url.Address, url.Amount);
|
bool receivedPayment = false;
|
||||||
Thread.Sleep(5000);
|
bool paid = false;
|
||||||
callbackServer.ProcessNextRequest((ctx) =>
|
bool confirmed = false;
|
||||||
|
bool completed = false;
|
||||||
|
while (!completed || !confirmed)
|
||||||
{
|
{
|
||||||
var ipn = new StreamReader(ctx.Request.Body).ReadToEnd();
|
var request = await callbackServer.GetNextRequest();
|
||||||
JsonConvert.DeserializeObject<InvoicePaymentNotification>(ipn); //can deserialize
|
if (request.ContainsKey("event"))
|
||||||
});
|
{
|
||||||
|
var evtName = request["event"]["name"].Value<string>();
|
||||||
|
switch (evtName)
|
||||||
|
{
|
||||||
|
case "invoice_created":
|
||||||
|
tester.ExplorerNode.SendToAddress(url.Address, url.Amount);
|
||||||
|
break;
|
||||||
|
case "invoice_receivedPayment":
|
||||||
|
receivedPayment = true;
|
||||||
|
break;
|
||||||
|
case "invoice_paidInFull":
|
||||||
|
Assert.True(receivedPayment);
|
||||||
|
tester.ExplorerNode.Generate(6);
|
||||||
|
paid = true;
|
||||||
|
break;
|
||||||
|
case "invoice_confirmed":
|
||||||
|
Assert.True(paid);
|
||||||
|
confirmed = true;
|
||||||
|
break;
|
||||||
|
case "invoice_completed":
|
||||||
|
Assert.True(paid); //TODO: Fix, out of order event mean we can receive invoice_confirmed after invoice_complete
|
||||||
|
completed = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Assert.False(true, $"{evtName} was not expected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
var invoice2 = acc.BitPay.GetInvoice(invoice.Id);
|
var invoice2 = acc.BitPay.GetInvoice(invoice.Id);
|
||||||
Assert.NotNull(invoice2);
|
Assert.NotNull(invoice2);
|
||||||
}
|
}
|
||||||
@@ -726,6 +759,7 @@ namespace BTCPayServer.Tests
|
|||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
user.RegisterDerivationScheme("BTC");
|
user.RegisterDerivationScheme("BTC");
|
||||||
|
user.SetNetworkFeeMode(NetworkFeeMode.Always);
|
||||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Price = 5000.0m,
|
Price = 5000.0m,
|
||||||
@@ -1591,13 +1625,19 @@ donation:
|
|||||||
[Trait("Integration", "Integration")]
|
[Trait("Integration", "Integration")]
|
||||||
public void CanExportInvoicesJson()
|
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())
|
using (var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
user.RegisterDerivationScheme("BTC");
|
user.RegisterDerivationScheme("BTC");
|
||||||
|
user.SetNetworkFeeMode(NetworkFeeMode.Always);
|
||||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Price = 10,
|
Price = 10,
|
||||||
@@ -1608,8 +1648,7 @@ donation:
|
|||||||
FullNotifications = true
|
FullNotifications = true
|
||||||
}, Facade.Merchant);
|
}, 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
|
// ensure 0 invoices exported because there are no payments yet
|
||||||
var jsonResult = user.GetController<InvoiceController>().Export("json").GetAwaiter().GetResult();
|
var jsonResult = user.GetController<InvoiceController>().Export("json").GetAwaiter().GetResult();
|
||||||
var result = Assert.IsType<ContentResult>(jsonResult);
|
var result = Assert.IsType<ContentResult>(jsonResult);
|
||||||
@@ -1619,7 +1658,7 @@ donation:
|
|||||||
var cashCow = tester.ExplorerNode;
|
var cashCow = tester.ExplorerNode;
|
||||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
|
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);
|
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||||
Thread.Sleep(1000); // prevent race conditions, ordering payments
|
Thread.Sleep(1000); // prevent race conditions, ordering payments
|
||||||
// look if you can reduce thread sleep, this was min value for me
|
// look if you can reduce thread sleep, this was min value for me
|
||||||
@@ -1629,7 +1668,7 @@ donation:
|
|||||||
Thread.Sleep(1000);
|
Thread.Sleep(1000);
|
||||||
|
|
||||||
// pay remaining amount
|
// pay remaining amount
|
||||||
cashCow.SendToAddress(invoiceAddress, 4*networkFee);
|
cashCow.SendToAddress(invoiceAddress, 4 * networkFee);
|
||||||
Thread.Sleep(1000);
|
Thread.Sleep(1000);
|
||||||
|
|
||||||
TestUtils.Eventually(() =>
|
TestUtils.Eventually(() =>
|
||||||
@@ -1641,21 +1680,102 @@ donation:
|
|||||||
var parsedJson = JsonConvert.DeserializeObject<object[]>(paidresult.Content);
|
var parsedJson = JsonConvert.DeserializeObject<object[]>(paidresult.Content);
|
||||||
Assert.Equal(3, parsedJson.Length);
|
Assert.Equal(3, parsedJson.Length);
|
||||||
|
|
||||||
|
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.Contains("\"InvoiceDue\": 1.5", pay1str);
|
Assert.Equal(invoiceDueAfterFirstPayment, GetFieldValue(pay1str, "InvoiceDue"));
|
||||||
Assert.Contains("\"InvoicePrice\": 10.0", pay1str);
|
Assert.Contains("\"InvoicePrice\": 10.0", pay1str);
|
||||||
Assert.Contains("\"ConversionRate\": 5000.0", pay1str);
|
Assert.Contains("\"ConversionRate\": 5000.0", pay1str);
|
||||||
Assert.Contains($"\"InvoiceId\": \"{invoice.Id}\",", pay1str);
|
Assert.Contains($"\"InvoiceId\": \"{invoice.Id}\",", pay1str);
|
||||||
|
|
||||||
var pay2str = parsedJson[1].ToString();
|
var pay2str = parsedJson[1].ToString();
|
||||||
Assert.Contains("\"InvoiceDue\": 1.5", pay2str);
|
Assert.Equal(invoiceDueAfterFirstPayment, GetFieldValue(pay2str, "InvoiceDue"));
|
||||||
|
|
||||||
var pay3str = parsedJson[2].ToString();
|
var pay3str = parsedJson[2].ToString();
|
||||||
Assert.Contains("\"InvoiceDue\": 0", pay3str);
|
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<NetworkFeeMode>())
|
||||||
|
{
|
||||||
|
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]
|
[Fact]
|
||||||
[Trait("Integration", "Integration")]
|
[Trait("Integration", "Integration")]
|
||||||
@@ -1667,7 +1787,7 @@ donation:
|
|||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
user.RegisterDerivationScheme("BTC");
|
user.RegisterDerivationScheme("BTC");
|
||||||
|
user.SetNetworkFeeMode(NetworkFeeMode.Always);
|
||||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Price = 500,
|
Price = 500,
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ namespace BTCPayServer.Controllers
|
|||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
Status = invoice.StatusString,
|
Status = invoice.StatusString,
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
NetworkFee = paymentMethodDetails.GetTxFee(),
|
NetworkFee = paymentMethodDetails.GetNetworkFee(),
|
||||||
IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
|
IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
|
||||||
ChangellyEnabled = changelly != null,
|
ChangellyEnabled = changelly != null,
|
||||||
ChangellyMerchantId = changelly?.ChangellyMerchantId,
|
ChangellyMerchantId = changelly?.ChangellyMerchantId,
|
||||||
|
|||||||
@@ -200,8 +200,6 @@ namespace BTCPayServer.Controllers
|
|||||||
paymentMethod.SetId(supportedPaymentMethod.PaymentId);
|
paymentMethod.SetId(supportedPaymentMethod.PaymentId);
|
||||||
paymentMethod.Rate = rate.BidAsk.Bid;
|
paymentMethod.Rate = rate.BidAsk.Bid;
|
||||||
var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, store, network, preparePayment);
|
var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, store, network, preparePayment);
|
||||||
if (storeBlob.NetworkFeeDisabled)
|
|
||||||
paymentDetails.SetNoTxFee();
|
|
||||||
paymentMethod.SetPaymentMethodDetails(paymentDetails);
|
paymentMethod.SetPaymentMethodDetails(paymentDetails);
|
||||||
|
|
||||||
Func<Money, Money, bool> compare = null;
|
Func<Money, Money, bool> compare = null;
|
||||||
@@ -241,7 +239,7 @@ namespace BTCPayServer.Controllers
|
|||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
if (paymentMethod.GetId().IsBTCOnChain)
|
if (paymentMethod.GetId().IsBTCOnChain)
|
||||||
{
|
{
|
||||||
entity.TxFee = paymentMethod.TxFee;
|
entity.TxFee = paymentMethod.NetworkFee;
|
||||||
entity.Rate = paymentMethod.Rate;
|
entity.Rate = paymentMethod.Rate;
|
||||||
entity.DepositAddress = paymentMethod.DepositAddress;
|
entity.DepositAddress = paymentMethod.DepositAddress;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ namespace BTCPayServer.Controllers
|
|||||||
vm.Id = store.Id;
|
vm.Id = store.Id;
|
||||||
vm.StoreName = store.StoreName;
|
vm.StoreName = store.StoreName;
|
||||||
vm.StoreWebsite = store.StoreWebsite;
|
vm.StoreWebsite = store.StoreWebsite;
|
||||||
vm.NetworkFee = !storeBlob.NetworkFeeDisabled;
|
vm.NetworkFeeMode = storeBlob.NetworkFeeMode;
|
||||||
vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice;
|
vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice;
|
||||||
vm.SpeedPolicy = store.SpeedPolicy;
|
vm.SpeedPolicy = store.SpeedPolicy;
|
||||||
vm.CanDelete = _Repo.CanDeleteStores();
|
vm.CanDelete = _Repo.CanDeleteStores();
|
||||||
@@ -489,7 +489,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
var blob = StoreData.GetStoreBlob();
|
var blob = StoreData.GetStoreBlob();
|
||||||
blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice;
|
blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice;
|
||||||
blob.NetworkFeeDisabled = !model.NetworkFee;
|
blob.NetworkFeeMode = model.NetworkFeeMode;
|
||||||
blob.MonitoringExpiration = model.MonitoringExpiration;
|
blob.MonitoringExpiration = model.MonitoringExpiration;
|
||||||
blob.InvoiceExpiration = model.InvoiceExpiration;
|
blob.InvoiceExpiration = model.InvoiceExpiration;
|
||||||
blob.LightningDescriptionTemplate = model.LightningDescriptionTemplate ?? string.Empty;
|
blob.LightningDescriptionTemplate = model.LightningDescriptionTemplate ?? string.Empty;
|
||||||
|
|||||||
@@ -249,6 +249,12 @@ namespace BTCPayServer.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum NetworkFeeMode
|
||||||
|
{
|
||||||
|
MultiplePaymentsOnly,
|
||||||
|
Always,
|
||||||
|
Never
|
||||||
|
}
|
||||||
public class StoreBlob
|
public class StoreBlob
|
||||||
{
|
{
|
||||||
public StoreBlob()
|
public StoreBlob()
|
||||||
@@ -258,10 +264,21 @@ namespace BTCPayServer.Data
|
|||||||
PaymentTolerance = 0;
|
PaymentTolerance = 0;
|
||||||
RequiresRefundEmail = true;
|
RequiresRefundEmail = true;
|
||||||
}
|
}
|
||||||
public bool NetworkFeeDisabled
|
|
||||||
|
[Obsolete("Use NetworkFeeMode instead")]
|
||||||
|
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||||
|
public bool? NetworkFeeDisabled
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||||
|
public NetworkFeeMode NetworkFeeMode
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
public bool RequiresRefundEmail { get; set; }
|
public bool RequiresRefundEmail { get; set; }
|
||||||
|
|
||||||
public string DefaultLang { get; set; }
|
public string DefaultLang { get; set; }
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ namespace BTCPayServer.HostedServices
|
|||||||
settings.ConvertMultiplierToSpread = true;
|
settings.ConvertMultiplierToSpread = true;
|
||||||
await _Settings.UpdateSetting(settings);
|
await _Settings.UpdateSetting(settings);
|
||||||
}
|
}
|
||||||
|
if (!settings.ConvertNetworkFeeProperty)
|
||||||
|
{
|
||||||
|
await ConvertNetworkFeeProperty();
|
||||||
|
settings.ConvertNetworkFeeProperty = true;
|
||||||
|
await _Settings.UpdateSetting(settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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()
|
private async Task ConvertMultiplierToSpread()
|
||||||
{
|
{
|
||||||
using (var ctx = _DBContextFactory.CreateContext())
|
using (var ctx = _DBContextFactory.CreateContext())
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Display(Name = "Add network fee to invoice (vary with mining fees)")]
|
[Display(Name = "Add additional fee (network fee) to invoice...")]
|
||||||
public bool NetworkFee
|
public Data.NetworkFeeMode NetworkFeeMode
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,27 +20,21 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
return DepositAddress;
|
return DepositAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public decimal GetTxFee()
|
public decimal GetNetworkFee()
|
||||||
{
|
{
|
||||||
return TxFee.ToDecimal(MoneyUnit.BTC);
|
return NetworkFee.ToDecimal(MoneyUnit.BTC);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetNoTxFee()
|
|
||||||
{
|
|
||||||
TxFee = Money.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void SetPaymentDestination(string newPaymentDestination)
|
public void SetPaymentDestination(string newPaymentDestination)
|
||||||
{
|
{
|
||||||
DepositAddress = newPaymentDestination;
|
DepositAddress = newPaymentDestination;
|
||||||
}
|
}
|
||||||
|
public Data.NetworkFeeMode NetworkFeeMode { get; set; }
|
||||||
|
|
||||||
// Those properties are JsonIgnore because their data is inside CryptoData class for legacy reason
|
// Those properties are JsonIgnore because their data is inside CryptoData class for legacy reason
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public FeeRate FeeRate { get; set; }
|
public FeeRate FeeRate { get; set; }
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Money TxFee { get; set; }
|
public Money NetworkFee { get; set; }
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public String DepositAddress { get; set; }
|
public String DepositAddress { get; set; }
|
||||||
public BitcoinAddress GetDepositAddress(Network network)
|
public BitcoinAddress GetDepositAddress(Network network)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
public TxOut Output { get; set; }
|
public TxOut Output { get; set; }
|
||||||
public int ConfirmationCount { get; set; }
|
public int ConfirmationCount { get; set; }
|
||||||
public bool RBF { get; set; }
|
public bool RBF { get; set; }
|
||||||
|
public decimal NetworkFee { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is set to true if the payment was created before CryptoPaymentData existed in BTCPayServer
|
/// This is set to true if the payment was created before CryptoPaymentData existed in BTCPayServer
|
||||||
|
|||||||
@@ -48,8 +48,18 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
throw new PaymentMethodUnavailableException($"Full node not available");
|
throw new PaymentMethodUnavailableException($"Full node not available");
|
||||||
var prepare = (Prepare)preparePaymentObject;
|
var prepare = (Prepare)preparePaymentObject;
|
||||||
Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod onchainMethod = new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod();
|
Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod onchainMethod = new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod();
|
||||||
|
onchainMethod.NetworkFeeMode = store.GetStoreBlob().NetworkFeeMode;
|
||||||
onchainMethod.FeeRate = await prepare.GetFeeRate;
|
onchainMethod.FeeRate = await prepare.GetFeeRate;
|
||||||
onchainMethod.TxFee = 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();
|
onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString();
|
||||||
return onchainMethod;
|
return onchainMethod;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
var alreadyExist = GetAllBitcoinPaymentData(invoice).Where(c => c.GetPaymentId() == paymentData.GetPaymentId()).Any();
|
var alreadyExist = GetAllBitcoinPaymentData(invoice).Where(c => c.GetPaymentId() == paymentData.GetPaymentId()).Any();
|
||||||
if (!alreadyExist)
|
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)
|
if(payment != null)
|
||||||
await ReceivedPayment(wallet, invoice, payment, evt.DerivationStrategy);
|
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 transaction = await wallet.GetTransactionAsync(coin.Coin.Outpoint.Hash);
|
||||||
var paymentData = new BitcoinLikePaymentData(coin.Coin, transaction.Transaction.RBF);
|
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);
|
alreadyAccounted.Add(coin.Coin.Outpoint);
|
||||||
if (payment != null)
|
if (payment != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ namespace BTCPayServer.Payments
|
|||||||
/// Returns what a merchant would need to pay to cashout this payment
|
/// Returns what a merchant would need to pay to cashout this payment
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
decimal GetTxFee();
|
decimal GetNetworkFee();
|
||||||
void SetNoTxFee();
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Change the payment destination (internal plumbing)
|
/// Change the payment destination (internal plumbing)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
return GetPaymentId();
|
return GetPaymentId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public decimal NetworkFee { get; set; }
|
||||||
|
|
||||||
public string GetPaymentId()
|
public string GetPaymentId()
|
||||||
{
|
{
|
||||||
return BOLT11;
|
return BOLT11;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using NBitcoin;
|
||||||
|
|
||||||
namespace BTCPayServer.Payments.Lightning
|
namespace BTCPayServer.Payments.Lightning
|
||||||
{
|
{
|
||||||
@@ -21,15 +22,10 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
return PaymentTypes.LightningLike;
|
return PaymentTypes.LightningLike;
|
||||||
}
|
}
|
||||||
|
|
||||||
public decimal GetTxFee()
|
public decimal GetNetworkFee()
|
||||||
{
|
{
|
||||||
return 0.0m;
|
return 0.0m;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetNoTxFee()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetPaymentDestination(string newPaymentDestination)
|
public void SetPaymentDestination(string newPaymentDestination)
|
||||||
{
|
{
|
||||||
BOLT11 = newPaymentDestination;
|
BOLT11 = newPaymentDestination;
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
{
|
{
|
||||||
BOLT11 = notification.BOLT11,
|
BOLT11 = notification.BOLT11,
|
||||||
Amount = notification.Amount
|
Amount = notification.Amount
|
||||||
}, network.CryptoCode, accounted: true);
|
}, network, accounted: true);
|
||||||
if (payment != null)
|
if (payment != null)
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(listenedInvoice.InvoiceId);
|
var invoice = await _InvoiceRepository.GetInvoice(listenedInvoice.InvoiceId);
|
||||||
|
|||||||
@@ -65,8 +65,7 @@ namespace BTCPayServer.Services.Invoices.Export
|
|||||||
var pdata = payment.GetCryptoPaymentData();
|
var pdata = payment.GetCryptoPaymentData();
|
||||||
|
|
||||||
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId(), Networks);
|
var pmethod = invoice.GetPaymentMethod(payment.GetPaymentMethodId(), Networks);
|
||||||
var paymentFee = pmethod.GetPaymentMethodDetails().GetTxFee();
|
var paidAfterNetworkFees = pdata.GetValue() - payment.NetworkFee;
|
||||||
var paidAfterNetworkFees = pdata.GetValue() - paymentFee;
|
|
||||||
invoiceDue -= paidAfterNetworkFees * pmethod.Rate;
|
invoiceDue -= paidAfterNetworkFees * pmethod.Rate;
|
||||||
|
|
||||||
var target = new ExportInvoiceHolder
|
var target = new ExportInvoiceHolder
|
||||||
@@ -83,7 +82,7 @@ namespace BTCPayServer.Services.Invoices.Export
|
|||||||
// 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
|
||||||
// while looking just at export you could sum Paid and assume merchant "received payments"
|
// while looking just at export you could sum Paid and assume merchant "received payments"
|
||||||
NetworkFee = paymentFee.ToString(CultureInfo.InvariantCulture),
|
NetworkFee = payment.NetworkFee.ToString(CultureInfo.InvariantCulture),
|
||||||
InvoiceDue = invoiceDue,
|
InvoiceDue = invoiceDue,
|
||||||
OrderId = invoice.OrderId,
|
OrderId = invoice.OrderId,
|
||||||
StoreId = invoice.StoreId,
|
StoreId = invoice.StoreId,
|
||||||
|
|||||||
@@ -486,7 +486,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public PaymentMethodDictionary GetPaymentMethods(BTCPayNetworkProvider networkProvider)
|
public PaymentMethodDictionary GetPaymentMethods(BTCPayNetworkProvider networkProvider)
|
||||||
{
|
{
|
||||||
PaymentMethodDictionary rates = new PaymentMethodDictionary(networkProvider);
|
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||||
var serializer = new Serializer(Dummy);
|
var serializer = new Serializer(Dummy);
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
if (PaymentMethod != null)
|
if (PaymentMethod != null)
|
||||||
@@ -500,11 +500,11 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
r.ParentEntity = this;
|
r.ParentEntity = this;
|
||||||
r.Network = networkProvider?.GetNetwork(r.CryptoCode);
|
r.Network = networkProvider?.GetNetwork(r.CryptoCode);
|
||||||
if (r.Network != null || networkProvider == null)
|
if (r.Network != null || networkProvider == null)
|
||||||
rates.Add(r);
|
paymentMethods.Add(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
return rates;
|
return paymentMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
Network Dummy = Network.Main;
|
Network Dummy = Network.Main;
|
||||||
@@ -518,8 +518,6 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public void SetPaymentMethods(PaymentMethodDictionary paymentMethods)
|
public void SetPaymentMethods(PaymentMethodDictionary paymentMethods)
|
||||||
{
|
{
|
||||||
if (paymentMethods.NetworkProvider != null)
|
|
||||||
throw new InvalidOperationException($"{nameof(paymentMethods)} should have NetworkProvider to null");
|
|
||||||
var obj = new JObject();
|
var obj = new JObject();
|
||||||
var serializer = new Serializer(Dummy);
|
var serializer = new Serializer(Dummy);
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
@@ -732,7 +730,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
{
|
{
|
||||||
FeeRate = FeeRate,
|
FeeRate = FeeRate,
|
||||||
DepositAddress = string.IsNullOrEmpty(DepositAddress) ? null : DepositAddress,
|
DepositAddress = string.IsNullOrEmpty(DepositAddress) ? null : DepositAddress,
|
||||||
TxFee = TxFee
|
NetworkFee = NetworkFee
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -740,7 +738,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
var details = PaymentMethodExtensions.DeserializePaymentMethodDetails(GetId(), PaymentMethodDetails);
|
var details = PaymentMethodExtensions.DeserializePaymentMethodDetails(GetId(), PaymentMethodDetails);
|
||||||
if (details is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod btcLike)
|
if (details is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod btcLike)
|
||||||
{
|
{
|
||||||
btcLike.TxFee = TxFee;
|
btcLike.NetworkFee = NetworkFee;
|
||||||
btcLike.DepositAddress = string.IsNullOrEmpty(DepositAddress) ? null : DepositAddress;
|
btcLike.DepositAddress = string.IsNullOrEmpty(DepositAddress) ? null : DepositAddress;
|
||||||
btcLike.FeeRate = FeeRate;
|
btcLike.FeeRate = FeeRate;
|
||||||
}
|
}
|
||||||
@@ -762,7 +760,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
if (paymentMethod is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod bitcoinPaymentMethod)
|
if (paymentMethod is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod bitcoinPaymentMethod)
|
||||||
{
|
{
|
||||||
TxFee = bitcoinPaymentMethod.TxFee;
|
NetworkFee = bitcoinPaymentMethod.NetworkFee;
|
||||||
FeeRate = bitcoinPaymentMethod.FeeRate;
|
FeeRate = bitcoinPaymentMethod.FeeRate;
|
||||||
DepositAddress = bitcoinPaymentMethod.DepositAddress;
|
DepositAddress = bitcoinPaymentMethod.DepositAddress;
|
||||||
}
|
}
|
||||||
@@ -778,7 +776,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
public FeeRate FeeRate { get; set; }
|
public FeeRate FeeRate { get; set; }
|
||||||
[JsonProperty(PropertyName = "txFee")]
|
[JsonProperty(PropertyName = "txFee")]
|
||||||
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).TxFee")]
|
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).TxFee")]
|
||||||
public Money TxFee { get; set; }
|
public Money NetworkFee { get; set; }
|
||||||
[JsonProperty(PropertyName = "depositAddress")]
|
[JsonProperty(PropertyName = "depositAddress")]
|
||||||
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).DepositAddress")]
|
[Obsolete("Use ((BitcoinLikeOnChainPaymentMethod)GetPaymentMethod()).DepositAddress")]
|
||||||
public string DepositAddress { get; set; }
|
public string DepositAddress { get; set; }
|
||||||
@@ -802,7 +800,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
.OrderBy(p => p.ReceivedTime)
|
.OrderBy(p => p.ReceivedTime)
|
||||||
.Select(_ =>
|
.Select(_ =>
|
||||||
{
|
{
|
||||||
var txFee = _.GetValue(paymentMethods, GetId(), paymentMethods[_.GetPaymentMethodId()].GetTxFee());
|
var txFee = _.GetValue(paymentMethods, GetId(), _.NetworkFee);
|
||||||
paid += _.GetValue(paymentMethods, GetId());
|
paid += _.GetValue(paymentMethods, GetId());
|
||||||
if (!paidEnough)
|
if (!paidEnough)
|
||||||
{
|
{
|
||||||
@@ -843,17 +841,18 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
var method = GetPaymentMethodDetails();
|
var method = GetPaymentMethodDetails();
|
||||||
if (method == null)
|
if (method == null)
|
||||||
return 0.0m;
|
return 0.0m;
|
||||||
return method.GetTxFee();
|
return method.GetNetworkFee();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PaymentEntity
|
public class PaymentEntity
|
||||||
{
|
{
|
||||||
|
public int Version { get; set; }
|
||||||
public DateTimeOffset ReceivedTime
|
public DateTimeOffset ReceivedTime
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
public decimal NetworkFee { get; set; }
|
||||||
[Obsolete("Use ((BitcoinLikePaymentData)GetCryptoPaymentData()).Outpoint")]
|
[Obsolete("Use ((BitcoinLikePaymentData)GetCryptoPaymentData()).Outpoint")]
|
||||||
public OutPoint Outpoint
|
public OutPoint Outpoint
|
||||||
{
|
{
|
||||||
@@ -890,7 +889,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
if (string.IsNullOrEmpty(CryptoPaymentDataType))
|
if (string.IsNullOrEmpty(CryptoPaymentDataType))
|
||||||
{
|
{
|
||||||
// In case this is a payment done before this update, consider it unconfirmed with RBF for safety
|
// For invoices created when CryptoPaymentDataType was not existing, we just consider that it is a RBFed payment for safety
|
||||||
var paymentData = new Payments.Bitcoin.BitcoinLikePaymentData();
|
var paymentData = new Payments.Bitcoin.BitcoinLikePaymentData();
|
||||||
paymentData.Outpoint = Outpoint;
|
paymentData.Outpoint = Outpoint;
|
||||||
paymentData.Output = Output;
|
paymentData.Output = Output;
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ retry:
|
|||||||
return paymentMethod.GetPaymentMethodDetails().GetPaymentDestination();
|
return paymentMethod.GetPaymentMethodDetails().GetPaymentDestination();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> NewAddress(string invoiceId, IPaymentMethodDetails paymentMethod, BTCPayNetwork network)
|
public async Task<bool> NewAddress(string invoiceId, Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod paymentMethod, BTCPayNetwork network)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
@@ -206,14 +206,13 @@ retry:
|
|||||||
if (currencyData == null)
|
if (currencyData == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var existingPaymentMethod = currencyData.GetPaymentMethodDetails();
|
var existingPaymentMethod = (Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod)currencyData.GetPaymentMethodDetails();
|
||||||
if (existingPaymentMethod.GetPaymentDestination() != null)
|
if (existingPaymentMethod.GetPaymentDestination() != null)
|
||||||
{
|
{
|
||||||
MarkUnassigned(invoiceId, invoiceEntity, context, currencyData.GetId());
|
MarkUnassigned(invoiceId, invoiceEntity, context, currencyData.GetId());
|
||||||
}
|
}
|
||||||
|
|
||||||
existingPaymentMethod.SetPaymentDestination(paymentMethod.GetPaymentDestination());
|
existingPaymentMethod.SetPaymentDestination(paymentMethod.GetPaymentDestination());
|
||||||
|
|
||||||
currencyData.SetPaymentMethodDetails(existingPaymentMethod);
|
currencyData.SetPaymentMethodDetails(existingPaymentMethod);
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
if (network.IsBTC)
|
if (network.IsBTC)
|
||||||
@@ -379,11 +378,24 @@ retry:
|
|||||||
private InvoiceEntity ToEntity(Data.InvoiceData invoice)
|
private InvoiceEntity ToEntity(Data.InvoiceData invoice)
|
||||||
{
|
{
|
||||||
var entity = ToObject<InvoiceEntity>(invoice.Blob, null);
|
var entity = ToObject<InvoiceEntity>(invoice.Blob, null);
|
||||||
|
PaymentMethodDictionary paymentMethods = null;
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
entity.Payments = invoice.Payments.Select(p =>
|
entity.Payments = invoice.Payments.Select(p =>
|
||||||
{
|
{
|
||||||
var paymentEntity = ToObject<PaymentEntity>(p.Blob, null);
|
var paymentEntity = ToObject<PaymentEntity>(p.Blob, null);
|
||||||
paymentEntity.Accounted = p.Accounted;
|
paymentEntity.Accounted = p.Accounted;
|
||||||
|
|
||||||
|
// PaymentEntity on version 0 does not have their own fee, because it was assumed that the payment method have fixed fee.
|
||||||
|
// We want to hide this legacy detail in InvoiceRepository, so we fetch the fee from the PaymentMethod and assign it to the PaymentEntity.
|
||||||
|
if (paymentEntity.Version == 0)
|
||||||
|
{
|
||||||
|
if (paymentMethods == null)
|
||||||
|
paymentMethods = entity.GetPaymentMethods(null);
|
||||||
|
var paymentMethodDetails = paymentMethods.TryGet(paymentEntity.GetPaymentMethodId())?.GetPaymentMethodDetails();
|
||||||
|
if (paymentMethodDetails != null) // == null should never happen, but we never know.
|
||||||
|
paymentEntity.NetworkFee = paymentMethodDetails.GetNetworkFee();
|
||||||
|
}
|
||||||
|
|
||||||
return paymentEntity;
|
return paymentEntity;
|
||||||
})
|
})
|
||||||
.OrderBy(a => a.ReceivedTime).ToList();
|
.OrderBy(a => a.ReceivedTime).ToList();
|
||||||
@@ -552,21 +564,37 @@ retry:
|
|||||||
/// <param name="cryptoCode"></param>
|
/// <param name="cryptoCode"></param>
|
||||||
/// <param name="accounted"></param>
|
/// <param name="accounted"></param>
|
||||||
/// <returns>The PaymentEntity or null if already added</returns>
|
/// <returns>The PaymentEntity or null if already added</returns>
|
||||||
public async Task<PaymentEntity> AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, string cryptoCode, bool accounted = false)
|
public async Task<PaymentEntity> AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, BTCPayNetwork network, bool accounted = false)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _ContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
|
var invoice = context.Invoices.Find(invoiceId);
|
||||||
|
if (invoice == null)
|
||||||
|
return null;
|
||||||
|
InvoiceEntity invoiceEntity = ToObject<InvoiceEntity>(invoice.Blob, network.NBitcoinNetwork);
|
||||||
|
PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()), null);
|
||||||
|
IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
||||||
PaymentEntity entity = new PaymentEntity
|
PaymentEntity entity = new PaymentEntity
|
||||||
{
|
{
|
||||||
|
Version = 1,
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
CryptoCode = cryptoCode,
|
CryptoCode = network.CryptoCode,
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
ReceivedTime = date.UtcDateTime,
|
ReceivedTime = date.UtcDateTime,
|
||||||
Accounted = accounted
|
Accounted = accounted,
|
||||||
|
NetworkFee = paymentMethodDetails.GetNetworkFee()
|
||||||
};
|
};
|
||||||
entity.SetCryptoPaymentData(paymentData);
|
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
|
PaymentData data = new PaymentData
|
||||||
{
|
{
|
||||||
Id = paymentData.GetPaymentId(),
|
Id = paymentData.GetPaymentId(),
|
||||||
|
|||||||
@@ -15,13 +15,6 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaymentMethodDictionary(BTCPayNetworkProvider networkProvider)
|
|
||||||
{
|
|
||||||
NetworkProvider = networkProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public BTCPayNetworkProvider NetworkProvider { get; set; }
|
|
||||||
public PaymentMethod this[PaymentMethodId index]
|
public PaymentMethod this[PaymentMethodId index]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ namespace BTCPayServer.Services
|
|||||||
public bool UnreachableStoreCheck { get; set; }
|
public bool UnreachableStoreCheck { get; set; }
|
||||||
public bool DeprecatedLightningConnectionStringCheck { get; set; }
|
public bool DeprecatedLightningConnectionStringCheck { get; set; }
|
||||||
public bool ConvertMultiplierToSpread { get; set; }
|
public bool ConvertMultiplierToSpread { get; set; }
|
||||||
|
public bool ConvertNetworkFeeProperty { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,15 +43,15 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Button Size</label>
|
<label>Button Size</label>
|
||||||
<div style="vertical-align:top; font-size:12px; display:flex;">
|
<div style="vertical-align:top; font-size:12px; display:flex;">
|
||||||
<button class="btn" style="width:95px;height:40px;margin-right:40px;"
|
<button class="btn" style="width:146px;height:40px;margin-right:40px;"
|
||||||
v-on:click="inputChanges($event, 0)" v-bind:class="{'btn-primary': (srvModel.buttonSize == 0) }">
|
v-on:click="inputChanges($event, 0)" v-bind:class="{'btn-primary': (srvModel.buttonSize == 0) }">
|
||||||
146 x 40 px
|
146 x 40 px
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-default" style="width:126px;height:46px;margin-right:40px;"
|
<button class="btn btn-default" style="width:168px;height:46px;margin-right:40px;"
|
||||||
v-on:click="inputChanges($event, 1)" v-bind:class="{'btn-primary': (srvModel.buttonSize == 1) }">
|
v-on:click="inputChanges($event, 1)" v-bind:class="{'btn-primary': (srvModel.buttonSize == 1) }">
|
||||||
168 x 46 px
|
168 x 46 px
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-default" style="width:146px;height:57px;"
|
<button class="btn btn-default" style="width:209px;height:57px;"
|
||||||
v-on:click="inputChanges($event, 2)" v-bind:class="{'btn-primary': (srvModel.buttonSize == 2) }">
|
v-on:click="inputChanges($event, 2)" v-bind:class="{'btn-primary': (srvModel.buttonSize == 2) }">
|
||||||
209 x 57 px
|
209 x 57 px
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -43,9 +43,13 @@
|
|||||||
<span asp-validation-for="StoreWebsite" class="text-danger"></span>
|
<span asp-validation-for="StoreWebsite" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="NetworkFee"></label>
|
<label asp-for="NetworkFeeMode"></label>
|
||||||
<a href="https://docs.btcpayserver.org/faq-and-common-issues/faq-stores#add-network-fee-to-invoice-vary-with-mining-fees" target="_blank"><span class="fa fa-question-circle-o" title="More information..."></span></a>
|
<a href="https://docs.btcpayserver.org/faq-and-common-issues/faq-stores#add-network-fee-to-invoice-vary-with-mining-fees" target="_blank"><span class="fa fa-question-circle-o" title="More information..."></span></a>
|
||||||
<input asp-for="NetworkFee" type="checkbox" class="form-check" />
|
<select asp-for="NetworkFeeMode" class="form-control">
|
||||||
|
<option value="MultiplePaymentsOnly">... only if the customer makes more than one payment for the invoice</option>
|
||||||
|
<option value="Always">... on every payment</option>
|
||||||
|
<option value="Never">Never add network fee</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="AnyoneCanCreateInvoice"></label>
|
<label asp-for="AnyoneCanCreateInvoice"></label>
|
||||||
|
|||||||
Reference in New Issue
Block a user