diff --git a/BTCPayServer/Payments/Lightning/CLightning/ChargeClient.cs b/BTCPayServer/Payments/Lightning/CLightning/ChargeClient.cs index 262ad4f92..fbae6b1c0 100644 --- a/BTCPayServer/Payments/Lightning/CLightning/ChargeClient.cs +++ b/BTCPayServer/Payments/Lightning/CLightning/ChargeClient.cs @@ -37,9 +37,9 @@ namespace BTCPayServer.Payments.Lightning.CLightning this._Uri = uri; this._Network = network; if (uri.UserInfo == null) - throw new ArgumentException(paramName:nameof(uri), message:"User information not present in uri"); + throw new ArgumentException(paramName: nameof(uri), message: "User information not present in uri"); var userInfo = uri.UserInfo.Split(':'); - if(userInfo.Length != 2) + if (userInfo.Length != 2) throw new ArgumentException(paramName: nameof(uri), message: "User information not present in uri"); Credentials = new NetworkCredential(userInfo[0], userInfo[1]); } @@ -85,6 +85,18 @@ namespace BTCPayServer.Payments.Lightning.CLightning { return GetInfoAsync().GetAwaiter().GetResult(); } + + public async Task GetInvoice(string invoiceId, CancellationToken cancellation = default(CancellationToken)) + { + var request = CreateMessage(HttpMethod.Get, $"invoice/{invoiceId}"); + var message = await _Client.SendAsync(request, cancellation); + if (message.StatusCode == HttpStatusCode.NotFound) + return null; + message.EnsureSuccessStatusCode(); + var content = await message.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(content); + } + public async Task GetInfoAsync(CancellationToken cancellation = default(CancellationToken)) { var request = CreateMessage(HttpMethod.Get, "info"); diff --git a/BTCPayServer/Payments/Lightning/CLightning/ChargeSession.cs b/BTCPayServer/Payments/Lightning/CLightning/ChargeSession.cs index 596724054..fdf96f7bd 100644 --- a/BTCPayServer/Payments/Lightning/CLightning/ChargeSession.cs +++ b/BTCPayServer/Payments/Lightning/CLightning/ChargeSession.cs @@ -10,7 +10,7 @@ using Newtonsoft.Json; namespace BTCPayServer.Payments.Lightning.CLightning { - public class ChargeInvoiceNotification + public class ChargeInvoice { public string Id { get; set; } @@ -42,7 +42,7 @@ namespace BTCPayServer.Payments.Lightning.CLightning } ArraySegment _Buffer; - public async Task NextEvent(CancellationToken cancellation = default(CancellationToken)) + public async Task NextEvent(CancellationToken cancellation = default(CancellationToken)) { var buffer = _Buffer; var array = _Buffer.Array; @@ -96,10 +96,10 @@ namespace BTCPayServer.Payments.Lightning.CLightning } UTF8Encoding UTF8 = new UTF8Encoding(false, true); - private ChargeInvoiceNotification ParseMessage(ArraySegment buffer) + private ChargeInvoice ParseMessage(ArraySegment buffer) { var str = UTF8.GetString(buffer.Array, 0, buffer.Count); - return JsonConvert.DeserializeObject(str, new JsonSerializerSettings()); + return JsonConvert.DeserializeObject(str, new JsonSerializerSettings()); } private async Task CloseSocketAndThrow(WebSocketCloseStatus status, string description, CancellationToken cancellation) diff --git a/BTCPayServer/Payments/Lightning/ChargeListener.cs b/BTCPayServer/Payments/Lightning/ChargeListener.cs index bb6bd293c..a3e1fe964 100644 --- a/BTCPayServer/Payments/Lightning/ChargeListener.cs +++ b/BTCPayServer/Payments/Lightning/ChargeListener.cs @@ -44,24 +44,25 @@ namespace BTCPayServer.Payments.Lightning { if (inv.Name == "invoice_created") { - await EnsureListening(inv.InvoiceId); + await EnsureListening(inv.InvoiceId, false); } })); _ListenPoller = new Timer(async s => { await Task.WhenAll((await _InvoiceRepository.GetPendingInvoices()) - .Select(async invoiceId => await EnsureListening(invoiceId)) + .Select(async invoiceId => await EnsureListening(invoiceId, true)) .ToArray()); }, null, 0, (int)PollInterval.TotalMilliseconds); leases.Add(_ListenPoller); return Task.CompletedTask; } - private async Task EnsureListening(string invoiceId) + private async Task EnsureListening(string invoiceId, bool poll) { if (Listening(invoiceId)) return; + var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId); foreach (var paymentMethod in invoice.GetPaymentMethods(_NetworkProvider) .Where(c => c.GetId().PaymentType == PaymentTypes.LightningLike)) @@ -74,6 +75,7 @@ namespace BTCPayServer.Payments.Lightning if (lightningSupportedMethod == null) continue; var network = _NetworkProvider.GetNetwork(paymentMethod.GetId().CryptoCode); + var listenedInvoice = new ListenedInvoice() { Uri = lightningSupportedMethod.GetLightningChargeUrl(false).AbsoluteUri, @@ -83,6 +85,19 @@ namespace BTCPayServer.Payments.Lightning Network = network, InvoiceId = invoice.Id }; + + if (poll) + { + var charge = GetChargeClient(lightningSupportedMethod, network); + var chargeInvoice = await charge.GetInvoice(lightningMethod.InvoiceId); + if (chargeInvoice == null) + continue; + if(chargeInvoice.Status == "paid") + await AddPayment(network, chargeInvoice, listenedInvoice); + if (chargeInvoice.Status == "paid" || chargeInvoice.Status == "expired") + continue; + } + StartListening(listenedInvoice); } } @@ -110,7 +125,7 @@ namespace BTCPayServer.Payments.Lightning try { Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Start listening {supportedPaymentMethod.GetLightningChargeUrl(false)}"); - var charge = new ChargeClient(supportedPaymentMethod.GetLightningChargeUrl(true), network.NBitcoinNetwork); + var charge = GetChargeClient(supportedPaymentMethod, network); var session = await charge.Listen(_Cts.Token); while (true) { @@ -124,12 +139,7 @@ namespace BTCPayServer.Payments.Lightning { if (notification.Status == "paid" && notification.PaidAt.HasValue) { - await _InvoiceRepository.AddPayment(listenedInvoice.InvoiceId, notification.PaidAt.Value, new LightningLikePaymentData() - { - BOLT11 = notification.PaymentRequest, - Amount = notification.MilliSatoshi - }, network.CryptoCode, accounted: true); - _Aggregator.Publish(new InvoiceEvent(listenedInvoice.InvoiceId, 1002, "invoice_receivedPayment")); + await AddPayment(network, notification, listenedInvoice); if (DoneListening(listenedInvoice)) break; } @@ -151,6 +161,21 @@ namespace BTCPayServer.Payments.Lightning Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Stop listening {supportedPaymentMethod.GetLightningChargeUrl(false)}"); } + private async Task AddPayment(BTCPayNetwork network, ChargeInvoice notification, ListenedInvoice listenedInvoice) + { + await _InvoiceRepository.AddPayment(listenedInvoice.InvoiceId, notification.PaidAt.Value, new LightningLikePaymentData() + { + BOLT11 = notification.PaymentRequest, + Amount = notification.MilliSatoshi + }, network.CryptoCode, accounted: true); + _Aggregator.Publish(new InvoiceEvent(listenedInvoice.InvoiceId, 1002, "invoice_receivedPayment")); + } + + private static ChargeClient GetChargeClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network) + { + return new ChargeClient(supportedPaymentMethod.GetLightningChargeUrl(true), network.NBitcoinNetwork); + } + List _ListeningLightning = new List(); MultiValueDictionary _ListenedInvoiceByLightningUrl = new MultiValueDictionary(); Dictionary _ListenedInvoiceByChargeInvoiceId = new Dictionary();