2021-10-18 05:37:59 +02:00
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 ;
2021-11-04 08:21:01 +01:00
using BTCPayServer.Payments.Lightning ;
using BTCPayServer.Services ;
2021-10-18 05:37:59 +02:00
using LNURL ;
2021-11-04 08:21:01 +01:00
using Microsoft.AspNetCore.Authorization ;
2021-10-18 05:37:59 +02:00
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 ;
2021-11-04 08:21:01 +01:00
private readonly UserService _userService ;
private readonly IAuthorizationService _authorizationService ;
2021-10-18 05:37:59 +02:00
public LightningLikePayoutHandler ( BTCPayNetworkProvider btcPayNetworkProvider ,
2021-11-04 08:21:01 +01:00
IHttpClientFactory httpClientFactory , UserService userService , IAuthorizationService authorizationService )
2021-10-18 05:37:59 +02:00
{
_btcPayNetworkProvider = btcPayNetworkProvider ;
_httpClientFactory = httpClientFactory ;
2021-11-04 08:21:01 +01:00
_userService = userService ;
_authorizationService = authorizationService ;
2021-10-18 05:37:59 +02:00
}
public bool CanHandle ( PaymentMethodId paymentMethod )
{
2022-07-01 06:26:00 +02:00
return ( paymentMethod . PaymentType = = LightningPaymentType . Instance | | paymentMethod . PaymentType = = LNURLPayPaymentType . Instance ) & &
2021-10-18 05:37:59 +02:00
_btcPayNetworkProvider . GetNetwork < BTCPayNetwork > ( 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 ) ;
}
2022-01-24 20:17:09 +09:00
public async Task < ( IClaimDestination destination , string error ) > ParseClaimDestination ( PaymentMethodId paymentMethodId , string destination )
2021-10-18 05:37:59 +02:00
{
destination = destination . Trim ( ) ;
var network = _btcPayNetworkProvider . GetNetwork < BTCPayNetwork > ( paymentMethodId . CryptoCode ) ;
try
{
string lnurlTag = null ;
2022-07-15 05:37:47 +02:00
var lnurl = destination . IsValidEmail ( )
2021-10-18 05:37:59 +02:00
? 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 )
{
}
2021-10-29 14:50:18 +02:00
catch
2021-10-18 05:37:59 +02:00
{
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 ;
2021-12-31 16:59:02 +09:00
if ( result = = null )
2022-01-24 20:17:09 +09:00
return ( null , "A valid BOLT11 invoice or LNURL Pay or Lightning address was not provided." ) ;
2021-10-18 05:37:59 +02:00
if ( invoice . ExpiryDate . UtcDateTime < DateTime . UtcNow )
{
return ( null ,
"The BOLT11 invoice submitted has expired." ) ;
}
return ( result , null ) ;
}
2022-01-24 20:17:09 +09:00
public ( bool valid , string error ) ValidateClaimDestination ( IClaimDestination claimDestination , PullPaymentBlob pullPaymentBlob )
{
if ( claimDestination is not BoltInvoiceClaimDestination bolt )
return ( true , null ) ;
var invoice = bolt . PaymentRequest ;
2022-04-24 05:19:34 +02:00
if ( pullPaymentBlob is not null & & ( invoice . ExpiryDate . UtcDateTime - DateTime . UtcNow ) < pullPaymentBlob . BOLT11Expiration )
2022-01-24 20:17:09 +09:00
{
return ( false ,
$"The BOLT11 invoice must have an expiry date of at least {(long)pullPaymentBlob.BOLT11Expiration.TotalDays} days from submission (Provided was only {(invoice.ExpiryDate.UtcDateTime - DateTime.UtcNow).Days})." ) ;
}
return ( true , null ) ;
}
2021-10-18 05:37:59 +02:00
public IPayoutProof ParseProof ( PayoutData payout )
{
return null ;
}
public void StartBackgroundCheck ( Action < Type [ ] > subscribe )
{
}
public Task BackgroundCheck ( object o )
{
return Task . CompletedTask ;
}
public Task < decimal > GetMinimumPayoutAmount ( PaymentMethodId paymentMethodId , IClaimDestination claimDestination )
{
return Task . FromResult ( Money . Satoshis ( 1 ) . ToDecimal ( MoneyUnit . BTC ) ) ;
}
public Dictionary < PayoutState , List < ( string Action , string Text ) > > GetPayoutSpecificActions ( )
{
return new Dictionary < PayoutState , List < ( string Action , string Text ) > > ( ) ;
}
public Task < StatusMessageModel > DoSpecificAction ( string action , string [ ] payoutIds , string storeId )
{
return Task . FromResult < StatusMessageModel > ( null ) ;
}
2021-11-04 08:21:01 +01:00
public async Task < IEnumerable < PaymentMethodId > > GetSupportedPaymentMethods ( StoreData storeData )
2021-10-18 05:37:59 +02:00
{
2021-11-04 08:21:01 +01:00
var result = new List < PaymentMethodId > ( ) ;
2021-12-31 16:59:02 +09:00
var methods = storeData . GetEnabledPaymentMethods ( _btcPayNetworkProvider ) . Where ( id = > id . PaymentId . PaymentType = = LightningPaymentType . Instance ) . OfType < LightningSupportedPaymentMethod > ( ) ;
2021-11-04 08:21:01 +01:00
foreach ( LightningSupportedPaymentMethod supportedPaymentMethod in methods )
{
if ( ! supportedPaymentMethod . IsInternalNode )
{
result . Add ( supportedPaymentMethod . PaymentId ) ;
continue ;
}
foreach ( UserStore storeDataUserStore in storeData . UserStores )
{
2021-12-31 16:59:02 +09:00
if ( ! await _userService . IsAdminUser ( storeDataUserStore . ApplicationUserId ) )
continue ;
2021-11-04 08:21:01 +01:00
result . Add ( supportedPaymentMethod . PaymentId ) ;
break ;
}
2021-12-31 16:59:02 +09:00
2021-11-04 08:21:01 +01:00
}
return result ;
2021-10-18 05:37:59 +02:00
}
public Task < IActionResult > InitiatePayment ( PaymentMethodId paymentMethodId , string [ ] payoutIds )
{
return Task . FromResult < IActionResult > ( new RedirectToActionResult ( "ConfirmLightningPayout" ,
2022-01-07 12:32:00 +09:00
"UILightningLikePayout" , new { cryptoCode = paymentMethodId . CryptoCode , payoutIds } ) ) ;
2021-10-18 05:37:59 +02:00
}
}
}