#nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Client.Models; using BTCPayServer.Configuration; using BTCPayServer.Data; using BTCPayServer.Lightning; using BTCPayServer.Logging; using BTCPayServer.Models; using BTCPayServer.Models.InvoicingModels; using BTCPayServer.Services; using BTCPayServer.Services.Invoices; using Microsoft.Extensions.Options; namespace BTCPayServer.Payments.Lightning { public class LNURLPayPaymentHandler : PaymentMethodHandlerBase { private readonly BTCPayNetworkProvider _networkProvider; private readonly DisplayFormatter _displayFormatter; private readonly LightningLikePaymentHandler _lightningLikePaymentHandler; private readonly LightningClientFactoryService _lightningClientFactoryService; public LNURLPayPaymentHandler( BTCPayNetworkProvider networkProvider, DisplayFormatter displayFormatter, IOptions options, LightningLikePaymentHandler lightningLikePaymentHandler, LightningClientFactoryService lightningClientFactoryService) { _networkProvider = networkProvider; _displayFormatter = displayFormatter; _lightningLikePaymentHandler = lightningLikePaymentHandler; _lightningClientFactoryService = lightningClientFactoryService; Options = options; } public override PaymentType PaymentType => PaymentTypes.LightningLike; private const string UriScheme = "lightning:"; public IOptions Options { get; } public override async Task CreatePaymentMethodDetails( InvoiceLogs logs, LNURLPaySupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, Data.StoreData store, BTCPayNetwork network, object preparePaymentObject, IEnumerable invoicePaymentMethods) { var lnPmi = new PaymentMethodId(supportedPaymentMethod.CryptoCode, PaymentTypes.LightningLike); var lnSupported = store.GetSupportedPaymentMethods(_networkProvider) .OfType() .SingleOrDefault(method => method.PaymentId == lnPmi); if (lnSupported is null) { throw new PaymentMethodUnavailableException("LNURL requires a lightning node to be configured for the store."); } var nodeInfo = (await _lightningLikePaymentHandler.GetNodeInfo(lnSupported, _networkProvider.GetNetwork(supportedPaymentMethod.CryptoCode), logs, paymentMethod.PreferOnion)).FirstOrDefault(); return new LNURLPayPaymentMethodDetails() { Activated = true, LightningSupportedPaymentMethod = lnSupported, Bech32Mode = supportedPaymentMethod.UseBech32Scheme, NodeInfo = nodeInfo?.ToString() }; } public override IEnumerable GetSupportedPaymentMethods() { return _networkProvider .GetAll() .OfType() .Where(network => network.NBitcoinNetwork.Consensus.SupportSegwit && network.SupportLightning) .Select(network => new PaymentMethodId(network.CryptoCode, PaymentTypes.LNURLPay)); } public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse, StoreBlob storeBlob, IPaymentMethod paymentMethod) { var paymentMethodId = paymentMethod.GetId(); var network = _networkProvider.GetNetwork(model.CryptoCode); var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId); var lnurl = cryptoInfo.PaymentUrls?.AdditionalData.TryGetValue("LNURLP", out var lnurlpObj) is true ? lnurlpObj?.ToObject() : null; model.PaymentMethodName = GetPaymentMethodName(network); model.BtcAddress = lnurl?.Replace(UriScheme, ""); model.InvoiceBitcoinUrl = lnurl; model.InvoiceBitcoinUrlQR = lnurl?.ToUpperInvariant().Replace(UriScheme.ToUpperInvariant(), UriScheme); model.PeerInfo = ((LNURLPayPaymentMethodDetails)paymentMethod.GetPaymentMethodDetails()).NodeInfo; if (storeBlob.LightningAmountInSatoshi && model.CryptoCode == "BTC") { base.PreparePaymentModelForAmountInSats(model, paymentMethod, _displayFormatter); } } public override string GetCryptoImage(PaymentMethodId paymentMethodId) { var network = _networkProvider.GetNetwork(paymentMethodId.CryptoCode); return GetCryptoImage(network); } private string GetCryptoImage(BTCPayNetworkBase network) { return ((BTCPayNetwork)network).LightningImagePath; } public override string GetPaymentMethodName(PaymentMethodId paymentMethodId) { var network = _networkProvider.GetNetwork(paymentMethodId.CryptoCode); return GetPaymentMethodName(network); } public override CheckoutUIPaymentMethodSettings GetCheckoutUISettings() { return new CheckoutUIPaymentMethodSettings() { ExtensionPartial = "Lightning/LightningLikeMethodCheckout", CheckoutBodyVueComponentName = "LightningLikeMethodCheckout", CheckoutHeaderVueComponentName = "LightningLikeMethodCheckoutHeader", NoScriptPartialName = "Lightning/LightningLikeMethodCheckoutNoScript" }; } private string GetPaymentMethodName(BTCPayNetworkBase network) { return $"{network.DisplayName} (Lightning LNURL)"; } public override object PreparePayment(LNURLPaySupportedPaymentMethod supportedPaymentMethod, Data.StoreData store, BTCPayNetworkBase network) { // pass a non null obj, so that if lazy payment feature is used, it has a marker to trigger activation return new { }; } } }