mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-11 01:35:22 +01:00
Abstract ChargeClient to prepare for support of other lightning implementation
This commit is contained in:
parent
73cc75fe66
commit
2b2e12b290
20 changed files with 133 additions and 54 deletions
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using BTCPayServer.Payments.Lightning.Charge;
|
||||
using BTCPayServer.Payments.Lightning.CLightning;
|
||||
using NBitcoin;
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments.Lightning.CLightning;
|
||||
using BTCPayServer.Payments.Lightning.CLightning.RPC;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
|
|
|
@ -18,7 +18,8 @@ using System.Text;
|
|||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Globalization;
|
||||
using BTCPayServer.Payments.Lightning.CLightning.RPC;
|
||||
using BTCPayServer.Payments.Lightning.CLightning;
|
||||
using BTCPayServer.Payments.Lightning.Charge;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
|
@ -117,7 +118,7 @@ namespace BTCPayServer.Tests
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<Payments.Lightning.CLightning.GetInfoResponse> WaitLNSynched()
|
||||
private async Task<GetInfoResponse> WaitLNSynched()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
|
|
|
@ -68,7 +68,6 @@ namespace BTCPayServer
|
|||
public BTCPayDefaultSettings DefaultSettings { get; set; }
|
||||
public KeyPath CoinType { get; internal set; }
|
||||
public int MaxTrackedConfirmation { get; internal set; } = 6;
|
||||
public string CLightningNetworkName { get; internal set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
|
|
@ -28,10 +28,7 @@ namespace BTCPayServer
|
|||
CryptoImagePath = "imlegacy/bitcoin-symbol.svg",
|
||||
LightningImagePath = "imlegacy/btc-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType),
|
||||
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("0'") : new KeyPath("1'"),
|
||||
CLightningNetworkName = ChainType == ChainType.Main ? "bitcoin" :
|
||||
ChainType == ChainType.Test ? "testnet" :
|
||||
ChainType == ChainType.Regtest ? "regtest" : null
|
||||
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("0'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,7 @@ namespace BTCPayServer
|
|||
CryptoImagePath = "imlegacy/litecoin-symbol.svg",
|
||||
LightningImagePath = "imlegacy/ltc-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NBXplorerNetworkProvider.ChainType),
|
||||
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("2'") : new KeyPath("3'"),
|
||||
CLightningNetworkName = ChainType == ChainType.Main ? "litecoin" :
|
||||
ChainType == ChainType.Test ? "litecoin-testnet" : null
|
||||
CoinType = NBXplorerNetworkProvider.ChainType == ChainType.Main ? new KeyPath("2'") : new KeyPath("3'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace BTCPayServer.Controllers
|
|||
var network = vm.CryptoCurrency == null ? null : _ExplorerProvider.GetNetwork(vm.CryptoCurrency);
|
||||
vm.SetCryptoCurrencies(_NetworkProvider, vm.CryptoCurrency);
|
||||
vm.InternalLightningNode = GetInternalLightningNodeIfAuthorized();
|
||||
if (network == null || network.CLightningNetworkName == null)
|
||||
if (network == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.CryptoCurrency), "Invalid network");
|
||||
return View(vm);
|
||||
|
|
|
@ -147,7 +147,7 @@ namespace BTCPayServer.Hosting
|
|||
services.AddSingleton<IHostedService, Payments.Bitcoin.NBXplorerListener>();
|
||||
|
||||
services.AddSingleton<Payments.IPaymentMethodHandler<Payments.Lightning.LightningSupportedPaymentMethod>, Payments.Lightning.LightningLikePaymentHandler>();
|
||||
services.AddSingleton<IHostedService, Payments.Lightning.ChargeListener>();
|
||||
services.AddSingleton<IHostedService, Payments.Lightning.LightningListener>();
|
||||
|
||||
services.AddSingleton<IHostedService, NBXplorerWaiters>();
|
||||
services.AddSingleton<IHostedService, InvoiceNotificationManager>();
|
||||
|
|
|
@ -34,7 +34,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||
public void SetCryptoCurrencies(BTCPayNetworkProvider networkProvider, string selectedScheme)
|
||||
{
|
||||
var choices = networkProvider.GetAll()
|
||||
.Where(n => n.CLightningNetworkName != null)
|
||||
.Select(o => new Format() { Name = o.CryptoCode, Value = o.CryptoCode }).ToArray();
|
||||
var chosen = choices.FirstOrDefault(f => f.Name == selectedScheme) ?? choices.FirstOrDefault();
|
||||
CryptoCurrencies = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen);
|
||||
|
|
|
@ -6,12 +6,13 @@ using System.Net;
|
|||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments.Lightning.Charge;
|
||||
using NBitcoin;
|
||||
using NBitcoin.RPC;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning.RPC
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
{
|
||||
public class CLightningRPCClient
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning.RPC
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
{
|
||||
public class NodeInfo
|
||||
{
|
|
@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
|||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning.RPC
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
{
|
||||
public class ChannelInfo
|
||||
{
|
|
@ -13,9 +13,9 @@ using NBitcoin;
|
|||
using NBXplorer;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
namespace BTCPayServer.Payments.Lightning.Charge
|
||||
{
|
||||
public class ChargeClient
|
||||
public class ChargeClient : ILightningInvoiceClient
|
||||
{
|
||||
private Uri _Uri;
|
||||
public Uri Uri
|
||||
|
@ -126,5 +126,48 @@ namespace BTCPayServer.Payments.Lightning.CLightning
|
|||
uri += "/";
|
||||
return new Uri(uri + partialUrl);
|
||||
}
|
||||
|
||||
async Task<LightningInvoice> ILightningInvoiceClient.GetInvoice(string invoiceId, CancellationToken cancellation)
|
||||
{
|
||||
var invoice = await GetInvoice(invoiceId, cancellation);
|
||||
return ChargeClient.ToLightningInvoice(invoice);
|
||||
}
|
||||
|
||||
async Task<ILightningListenInvoiceSession> ILightningInvoiceClient.Listen(CancellationToken cancellation)
|
||||
{
|
||||
return await Listen(cancellation);
|
||||
}
|
||||
|
||||
internal static LightningInvoice ToLightningInvoice(ChargeInvoice invoice)
|
||||
{
|
||||
return new LightningInvoice()
|
||||
{
|
||||
Id = invoice.Id,
|
||||
Amount = invoice.MilliSatoshi,
|
||||
BOLT11 = invoice.PaymentRequest,
|
||||
PaidAt = invoice.PaidAt,
|
||||
Status = invoice.Status
|
||||
};
|
||||
}
|
||||
|
||||
async Task<LightningInvoice> ILightningInvoiceClient.CreateInvoice(LightMoney amount, TimeSpan expiry, CancellationToken cancellation)
|
||||
{
|
||||
var invoice = await CreateInvoiceAsync(new CreateInvoiceRequest() { Amont = amount, Expiry = expiry });
|
||||
return new LightningInvoice() { Id = invoice.Id, Amount = amount, BOLT11 = invoice.PayReq, Status = "unpaid" };
|
||||
}
|
||||
|
||||
async Task<LightningNodeInformation> ILightningInvoiceClient.GetInfo(CancellationToken cancellation)
|
||||
{
|
||||
var info = await GetInfoAsync(cancellation);
|
||||
var address = info.Address.Select(a => a.Address).FirstOrDefault();
|
||||
var port = info.Port;
|
||||
address = address ?? Uri.DnsSafeHost;
|
||||
return new LightningNodeInformation()
|
||||
{
|
||||
P2PPort = port,
|
||||
Address = address,
|
||||
BlockHeight = info.BlockHeight
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
|||
using NBXplorer;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
namespace BTCPayServer.Payments.Lightning.Charge
|
||||
{
|
||||
public class ChargeInvoice
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ namespace BTCPayServer.Payments.Lightning.CLightning
|
|||
[JsonProperty("payreq")]
|
||||
public string PaymentRequest { get; set; }
|
||||
}
|
||||
public class ChargeSession : IDisposable
|
||||
public class ChargeSession : ILightningListenInvoiceSession
|
||||
{
|
||||
private ClientWebSocket socket;
|
||||
|
||||
|
@ -42,7 +42,7 @@ namespace BTCPayServer.Payments.Lightning.CLightning
|
|||
}
|
||||
|
||||
ArraySegment<byte> _Buffer;
|
||||
public async Task<ChargeInvoice> NextEvent(CancellationToken cancellation = default(CancellationToken))
|
||||
public async Task<ChargeInvoice> WaitInvoice(CancellationToken cancellation = default(CancellationToken))
|
||||
{
|
||||
var buffer = _Buffer;
|
||||
var array = _Buffer.Array;
|
||||
|
@ -120,5 +120,15 @@ namespace BTCPayServer.Payments.Lightning.CLightning
|
|||
{
|
||||
await this.socket.CloseSocket();
|
||||
}
|
||||
|
||||
async Task<LightningInvoice> ILightningListenInvoiceSession.WaitInvoice(CancellationToken token)
|
||||
{
|
||||
return ChargeClient.ToLightningInvoice(await WaitInvoice(token));
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
namespace BTCPayServer.Payments.Lightning.Charge
|
||||
{
|
||||
public class CreateInvoiceRequest
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
namespace BTCPayServer.Payments.Lightning.Charge
|
||||
{
|
||||
public class CreateInvoiceResponse
|
||||
{
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning.CLightning
|
||||
namespace BTCPayServer.Payments.Lightning.Charge
|
||||
{
|
||||
//[{"type":"ipv4","address":"52.166.90.122","port":9735}]
|
||||
public class GetInfoResponse
|
40
BTCPayServer/Payments/Lightning/ILightningInvoiceClient.cs
Normal file
40
BTCPayServer/Payments/Lightning/ILightningInvoiceClient.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Payments.Lightning.Charge;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
public class LightningInvoice
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string BOLT11 { get; set; }
|
||||
public DateTimeOffset? PaidAt
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public LightMoney Amount { get; set; }
|
||||
}
|
||||
|
||||
public class LightningNodeInformation
|
||||
{
|
||||
public string Address { get; internal set; }
|
||||
public int P2PPort { get; internal set; }
|
||||
public int BlockHeight { get; set; }
|
||||
}
|
||||
public interface ILightningInvoiceClient
|
||||
{
|
||||
Task<LightningInvoice> GetInvoice(string invoiceId, CancellationToken cancellation = default(CancellationToken));
|
||||
Task<LightningInvoice> CreateInvoice(LightMoney amount, TimeSpan expiry, CancellationToken cancellation = default(CancellationToken));
|
||||
Task<ILightningListenInvoiceSession> Listen(CancellationToken cancellation = default(CancellationToken));
|
||||
Task<LightningNodeInformation> GetInfo(CancellationToken cancellation = default(CancellationToken));
|
||||
}
|
||||
|
||||
public interface ILightningListenInvoiceSession : IDisposable
|
||||
{
|
||||
Task<LightningInvoice> WaitInvoice(CancellationToken token);
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using System.Net.Sockets;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments.Lightning.Charge;
|
||||
using BTCPayServer.Payments.Lightning.CLightning;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
|
@ -24,14 +25,12 @@ namespace BTCPayServer.Payments.Lightning
|
|||
var due = Extensions.RoundUp(invoice.ProductInformation.Price / paymentMethod.Rate, 8);
|
||||
var client = GetClient(supportedPaymentMethod, network);
|
||||
var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow;
|
||||
var lightningInvoice = await client.CreateInvoiceAsync(new CreateInvoiceRequest()
|
||||
{
|
||||
Amont = new LightMoney(due, LightMoneyUnit.BTC),
|
||||
Expiry = expiry < TimeSpan.Zero ? TimeSpan.FromSeconds(1) : expiry
|
||||
});
|
||||
if (expiry < TimeSpan.Zero)
|
||||
expiry = TimeSpan.FromSeconds(1);
|
||||
var lightningInvoice = await client.CreateInvoice(new LightMoney(due, LightMoneyUnit.BTC), expiry);
|
||||
return new LightningLikePaymentMethodDetails()
|
||||
{
|
||||
BOLT11 = lightningInvoice.PayReq,
|
||||
BOLT11 = lightningInvoice.BOLT11,
|
||||
InvoiceId = lightningInvoice.Id
|
||||
};
|
||||
}
|
||||
|
@ -54,23 +53,15 @@ namespace BTCPayServer.Payments.Lightning
|
|||
|
||||
var cts = new CancellationTokenSource(5000);
|
||||
var client = GetClient(supportedPaymentMethod, network);
|
||||
GetInfoResponse info = null;
|
||||
LightningNodeInformation info = null;
|
||||
try
|
||||
{
|
||||
|
||||
info = await client.GetInfoAsync(cts.Token);
|
||||
info = await client.GetInfo(cts.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Error while connecting to the lightning charge {client.Uri} ({ex.Message})");
|
||||
}
|
||||
var address = info.Address.Select(a=>a.Address).FirstOrDefault();
|
||||
var port = info.Port;
|
||||
address = address ?? client.Uri.DnsSafeHost;
|
||||
|
||||
if (info.Network != network.CLightningNetworkName)
|
||||
{
|
||||
throw new Exception($"Lightning node network {info.Network}, but expected is {network.CLightningNetworkName}");
|
||||
throw new Exception($"Error while connecting to the API ({ex.Message})");
|
||||
}
|
||||
|
||||
var blocksGap = Math.Abs(info.BlockHeight - summary.Status.ChainHeight);
|
||||
|
@ -81,15 +72,15 @@ namespace BTCPayServer.Payments.Lightning
|
|||
|
||||
try
|
||||
{
|
||||
await TestConnection(address, port, cts.Token);
|
||||
await TestConnection(info.Address, info.P2PPort, cts.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Error while connecting to the lightning node via {address}:{port} ({ex.Message})");
|
||||
throw new Exception($"Error while connecting to the lightning node via {info.Address}:{info.P2PPort} ({ex.Message})");
|
||||
}
|
||||
}
|
||||
|
||||
private static ChargeClient GetClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
|
||||
private static ILightningInvoiceClient GetClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
|
||||
{
|
||||
return new ChargeClient(supportedPaymentMethod.GetLightningChargeUrl(true), network.NBitcoinNetwork);
|
||||
}
|
||||
|
|
|
@ -6,14 +6,14 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments.Lightning.CLightning;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using NBXplorer;
|
||||
using BTCPayServer.Payments.Lightning.Charge;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
public class ChargeListener : IHostedService
|
||||
public class LightningListener : IHostedService
|
||||
{
|
||||
class ListenedInvoice
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||
EventAggregator _Aggregator;
|
||||
InvoiceRepository _InvoiceRepository;
|
||||
BTCPayNetworkProvider _NetworkProvider;
|
||||
public ChargeListener(EventAggregator aggregator,
|
||||
public LightningListener(EventAggregator aggregator,
|
||||
InvoiceRepository invoiceRepository,
|
||||
BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
|
@ -128,13 +128,13 @@ namespace BTCPayServer.Payments.Lightning
|
|||
var session = await charge.Listen(_Cts.Token);
|
||||
while (true)
|
||||
{
|
||||
var notification = await session.NextEvent(_Cts.Token);
|
||||
var notification = await session.WaitInvoice(_Cts.Token);
|
||||
ListenedInvoice listenedInvoice = GetListenedInvoice(notification.Id);
|
||||
if (listenedInvoice == null)
|
||||
continue;
|
||||
|
||||
if (notification.Id == listenedInvoice.PaymentMethodDetails.InvoiceId &&
|
||||
notification.PaymentRequest == listenedInvoice.PaymentMethodDetails.BOLT11)
|
||||
notification.BOLT11 == listenedInvoice.PaymentMethodDetails.BOLT11)
|
||||
{
|
||||
if (notification.Status == "paid" && notification.PaidAt.HasValue)
|
||||
{
|
||||
|
@ -161,18 +161,18 @@ 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)
|
||||
private async Task AddPayment(BTCPayNetwork network, LightningInvoice notification, ListenedInvoice listenedInvoice)
|
||||
{
|
||||
var payment = await _InvoiceRepository.AddPayment(listenedInvoice.InvoiceId, notification.PaidAt.Value, new LightningLikePaymentData()
|
||||
{
|
||||
BOLT11 = notification.PaymentRequest,
|
||||
Amount = notification.MilliSatoshi
|
||||
BOLT11 = notification.BOLT11,
|
||||
Amount = notification.Amount
|
||||
}, network.CryptoCode, accounted: true);
|
||||
if(payment != null)
|
||||
_Aggregator.Publish(new InvoiceEvent(listenedInvoice.InvoiceId, 1002, "invoice_receivedPayment"));
|
||||
}
|
||||
|
||||
private static ChargeClient GetChargeClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
|
||||
private static ILightningInvoiceClient GetChargeClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
|
||||
{
|
||||
return new ChargeClient(supportedPaymentMethod.GetLightningChargeUrl(true), network.NBitcoinNetwork);
|
||||
}
|
Loading…
Add table
Reference in a new issue