Merge pull request #1913 from Kukks/ln-gen-new-partial

Ensure new bolt11 invoice on partial payment to btcpay invoice
This commit is contained in:
Nicolas Dorier
2020-09-22 20:56:56 +09:00
committed by GitHub
3 changed files with 111 additions and 4 deletions

View File

@@ -747,6 +747,51 @@ namespace BTCPayServer.Tests
Assert.True(tor.Services.Where(t => t.ServiceType == TorServiceType.Other).Count() > 1);
}
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]
public async Task EnsureNewLightningInvoiceOnPartialPayment()
{
using var tester = ServerTester.Create();
tester.ActivateLightning();
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
await user.GrantAccessAsync();
await user.RegisterDerivationSchemeAsync("BTC");
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
user.SetNetworkFeeMode(NetworkFeeMode.Never);
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.01m, "BTC"));
await tester.WaitForEvent<InvoiceNewAddressEvent>(async () =>
{
await tester.ExplorerNode.SendToAddressAsync(
BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest), Money.Coins(0.005m));
});
var newInvoice = await user.BitPay.GetInvoiceAsync(invoice.Id);
var newBolt11 = newInvoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
var oldBolt11= invoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
Assert.NotEqual(newBolt11,oldBolt11);
Assert.Equal(newInvoice.BtcDue.GetValue(), BOLT11PaymentRequest.Parse(newBolt11, Network.RegTest).MinimumAmount.ToDecimal(LightMoneyUnit.BTC));
var evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
{
await tester.SendLightningPaymentAsync(newInvoice);
});
Assert.Equal(evt.InvoiceId, invoice.Id);
Assert.Equal(InvoiceStatus.Complete, evt.State.Status);
Assert.Equal(InvoiceExceptionStatus.None, evt.State.ExceptionStatus);
evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
{
await tester.SendLightningPaymentAsync(invoice);
});
Assert.Equal(evt.InvoiceId, invoice.Id);
Assert.Equal(InvoiceStatus.Invalid, evt.State.Status);
Assert.Equal(InvoiceExceptionStatus.PaidOver, evt.State.ExceptionStatus);
}
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]

View File

@@ -47,8 +47,17 @@ namespace BTCPayServer.Payments.Lightning
//direct casting to (BTCPayNetwork) is fixed in other pull requests with better generic interfacing for handlers
var storeBlob = store.GetStoreBlob();
var test = GetNodeInfo(paymentMethod.PreferOnion, supportedPaymentMethod, network);
var invoice = paymentMethod.ParentEntity;
var due = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility);
decimal due = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility);
try
{
due = paymentMethod.Calculate().Due.ToDecimal(MoneyUnit.BTC);
}
catch (Exception)
{
// ignored
}
var client = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), network);
var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow;
if (expiry < TimeSpan.Zero)

View File

@@ -5,11 +5,14 @@ using System.Linq;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Lightning;
using BTCPayServer.Logging;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -24,6 +27,8 @@ namespace BTCPayServer.Payments.Lightning
private readonly IMemoryCache _memoryCache;
readonly BTCPayNetworkProvider _NetworkProvider;
private readonly LightningClientFactoryService lightningClientFactory;
private readonly LightningLikePaymentHandler _lightningLikePaymentHandler;
private readonly StoreRepository _storeRepository;
readonly Channel<string> _CheckInvoices = Channel.CreateUnbounded<string>();
Task _CheckingInvoice;
readonly Dictionary<(string, string), LightningInstanceListener> _InstanceListeners = new Dictionary<(string, string), LightningInstanceListener>();
@@ -32,13 +37,17 @@ namespace BTCPayServer.Payments.Lightning
InvoiceRepository invoiceRepository,
IMemoryCache memoryCache,
BTCPayNetworkProvider networkProvider,
LightningClientFactoryService lightningClientFactory)
LightningClientFactoryService lightningClientFactory,
LightningLikePaymentHandler lightningLikePaymentHandler,
StoreRepository storeRepository)
{
_Aggregator = aggregator;
_InvoiceRepository = invoiceRepository;
_memoryCache = memoryCache;
_NetworkProvider = networkProvider;
this.lightningClientFactory = lightningClientFactory;
_lightningLikePaymentHandler = lightningLikePaymentHandler;
_storeRepository = storeRepository;
}
async Task CheckingInvoice(CancellationToken cancellation)
@@ -54,7 +63,7 @@ namespace BTCPayServer.Payments.Lightning
if (!_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener) ||
!instanceListener.IsListening)
{
instanceListener = instanceListener ?? new LightningInstanceListener(_InvoiceRepository, _Aggregator, listenedInvoice.SupportedPaymentMethod, lightningClientFactory, listenedInvoice.Network);
instanceListener ??= new LightningInstanceListener(_InvoiceRepository, _Aggregator, listenedInvoice.SupportedPaymentMethod, lightningClientFactory, listenedInvoice.Network);
var status = await instanceListener.PollPayment(listenedInvoice, cancellation);
if (status is null ||
status is LightningInvoiceStatus.Paid ||
@@ -134,6 +143,51 @@ namespace BTCPayServer.Payments.Lightning
_CheckInvoices.Writer.TryWrite(inv.Invoice.Id);
}
}));
leases.Add(_Aggregator.Subscribe<Events.InvoiceDataChangedEvent>(async inv =>
{
if (inv.State.Status == InvoiceStatus.New &&
inv.State.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
{
var invoice = await _InvoiceRepository.GetInvoice(inv.InvoiceId);
var paymentMethods = invoice.GetPaymentMethods()
.Where(method => method.GetId().PaymentType == PaymentTypes.LightningLike).ToArray();
var store = await _storeRepository.FindStore(invoice.StoreId);
if (paymentMethods.Any())
{
var logs = new InvoiceLogs();
logs.Write("Partial payment detected, attempting to update all lightning payment methods with new bolt11 with correct due amount.", InvoiceEventData.EventSeverity.Info);
foreach (var paymentMethod in paymentMethods)
{
try
{
var supportedMethod =
invoice.GetSupportedPaymentMethod<LightningSupportedPaymentMethod>(
paymentMethod.GetId()).First();
var prepObj =
_lightningLikePaymentHandler.PreparePayment(supportedMethod, store,
paymentMethod.Network);
var newPaymentMethodDetails =
await _lightningLikePaymentHandler.CreatePaymentMethodDetails(
logs, supportedMethod,
paymentMethod, store, paymentMethod.Network, prepObj);
await _InvoiceRepository.NewAddress(invoice.Id, newPaymentMethodDetails,
paymentMethod.Network);
_Aggregator.Publish(new InvoiceNewAddressEvent(invoice.Id, newPaymentMethodDetails.GetPaymentDestination(),paymentMethod.Network ));
}
catch (Exception e)
{
logs.Write($"Could not update {paymentMethod.GetId().ToPrettyString()}: {e.Message}",
InvoiceEventData.EventSeverity.Error);
}
}
await _InvoiceRepository.AddInvoiceLogs(invoice.Id, logs);
_CheckInvoices.Writer.TryWrite(inv.InvoiceId);
}
}
}));
_CheckingInvoice = CheckingInvoice(_Cts.Token);
_ListenPoller = new Timer(async s =>
{
@@ -168,7 +222,6 @@ namespace BTCPayServer.Payments.Lightning
}
readonly CancellationTokenSource _Cts = new CancellationTokenSource();
readonly HashSet<string> _InvoiceIds = new HashSet<string>();
private Timer _ListenPoller;
public async Task StopAsync(CancellationToken cancellationToken)