using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using BTCPayServer.Abstractions.Models; using BTCPayServer.Client.Models; using BTCPayServer.Lightning; using BTCPayServer.Payments; using BTCPayServer.Payments.Lightning; using BTCPayServer.Services; using BTCPayServer.Validation; using LNURL; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using NBitcoin; namespace BTCPayServer.Data.Payouts.LightningLike { public class LightningLikePayoutHandler : IPayoutHandler { public const string LightningLikePayoutHandlerOnionNamedClient = nameof(LightningLikePayoutHandlerOnionNamedClient); public const string LightningLikePayoutHandlerClearnetNamedClient = nameof(LightningLikePayoutHandlerClearnetNamedClient); private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly IHttpClientFactory _httpClientFactory; private readonly UserService _userService; private readonly IAuthorizationService _authorizationService; public LightningLikePayoutHandler(BTCPayNetworkProvider btcPayNetworkProvider, IHttpClientFactory httpClientFactory, UserService userService, IAuthorizationService authorizationService) { _btcPayNetworkProvider = btcPayNetworkProvider; _httpClientFactory = httpClientFactory; _userService = userService; _authorizationService = authorizationService; } public bool CanHandle(PaymentMethodId paymentMethod) { return paymentMethod.PaymentType == LightningPaymentType.Instance && _btcPayNetworkProvider.GetNetwork(paymentMethod.CryptoCode)?.SupportLightning is true; } public Task TrackClaim(PaymentMethodId paymentMethodId, IClaimDestination claimDestination) { return Task.CompletedTask; } public HttpClient CreateClient(Uri uri) { return _httpClientFactory.CreateClient(uri.IsOnion() ? LightningLikePayoutHandlerOnionNamedClient : LightningLikePayoutHandlerClearnetNamedClient); } public async Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, bool validate) { destination = destination.Trim(); var network = _btcPayNetworkProvider.GetNetwork(paymentMethodId.CryptoCode); try { string lnurlTag = null; var lnurl = EmailValidator.IsEmail(destination) ? LNURL.LNURL.ExtractUriFromInternetIdentifier(destination) : LNURL.LNURL.Parse(destination, out lnurlTag); if (lnurlTag is null) { var info = (LNURLPayRequest)(await LNURL.LNURL.FetchInformation(lnurl, CreateClient(lnurl))); lnurlTag = info.Tag; } if (lnurlTag.Equals("payRequest", StringComparison.InvariantCultureIgnoreCase)) { return (new LNURLPayClaimDestinaton(destination), null); } } catch (FormatException) { } catch { return (null, "The LNURL / Lightning Address provided was not online."); } var result = BOLT11PaymentRequest.TryParse(destination, out var invoice, network.NBitcoinNetwork) ? new BoltInvoiceClaimDestination(destination, invoice) : null; if (result == null) return (null, "A valid BOLT11 invoice (with 30+ day expiry) or LNURL Pay or Lightning address was not provided."); if (validate && (invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days < 30) { return (null, $"The BOLT11 invoice must have an expiry date of at least 30 days from submission (Provided was only {(invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days})."); } if (invoice.ExpiryDate.UtcDateTime < DateTime.UtcNow) { return (null, "The BOLT11 invoice submitted has expired."); } return (result, null); } public IPayoutProof ParseProof(PayoutData payout) { return null; } public void StartBackgroundCheck(Action subscribe) { } public Task BackgroundCheck(object o) { return Task.CompletedTask; } public Task GetMinimumPayoutAmount(PaymentMethodId paymentMethodId, IClaimDestination claimDestination) { return Task.FromResult(Money.Satoshis(1).ToDecimal(MoneyUnit.BTC)); } public Dictionary> GetPayoutSpecificActions() { return new Dictionary>(); } public Task DoSpecificAction(string action, string[] payoutIds, string storeId) { return Task.FromResult(null); } public async Task> GetSupportedPaymentMethods(StoreData storeData) { var result = new List(); var methods = storeData.GetEnabledPaymentMethods(_btcPayNetworkProvider).Where(id => id.PaymentId.PaymentType == LightningPaymentType.Instance).OfType(); foreach (LightningSupportedPaymentMethod supportedPaymentMethod in methods) { if (!supportedPaymentMethod.IsInternalNode) { result.Add(supportedPaymentMethod.PaymentId); continue; } foreach (UserStore storeDataUserStore in storeData.UserStores) { if (!await _userService.IsAdminUser(storeDataUserStore.ApplicationUserId)) continue; result.Add(supportedPaymentMethod.PaymentId); break; } } return result; } public Task InitiatePayment(PaymentMethodId paymentMethodId, string[] payoutIds) { return Task.FromResult(new RedirectToActionResult("ConfirmLightningPayout", "LightningLikePayout", new { cryptoCode = paymentMethodId.CryptoCode, payoutIds })); } } }