mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-05 02:12:36 +01:00
Fix LN Address payouts (#3960)
* Fix LN Address payouts LN Address was validated when creating the claim but the paying sdection did not support it. * reuse code * reuse code * do not use mail directly * fix email validator
This commit is contained in:
parent
83c35328ed
commit
f8619e382b
4 changed files with 118 additions and 125 deletions
|
@ -9,7 +9,6 @@ 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;
|
||||
|
@ -64,7 +63,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
try
|
||||
{
|
||||
string lnurlTag = null;
|
||||
var lnurl = MailboxAddressValidator.IsMailboxAddress(destination)
|
||||
var lnurl = destination.IsValidEmail()
|
||||
? LNURL.LNURL.ExtractUriFromInternetIdentifier(destination)
|
||||
: LNURL.LNURL.Parse(destination, out lnurlTag);
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
|
@ -12,7 +11,6 @@ using BTCPayServer.Payments;
|
|||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using LNURL;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -20,6 +18,7 @@ using Microsoft.AspNetCore.Identity;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Data.Payouts.LightningLike
|
||||
{
|
||||
|
@ -36,7 +35,6 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
private readonly IOptions<LightningNetworkOptions> _options;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
|
||||
public UILightningLikePayoutController(ApplicationDbContextFactory applicationDbContextFactory,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
|
@ -44,7 +42,6 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
IEnumerable<IPayoutHandler> payoutHandlers,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
StoreRepository storeRepository,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
LightningClientFactoryService lightningClientFactoryService,
|
||||
IOptions<LightningNetworkOptions> options, IAuthorizationService authorizationService)
|
||||
{
|
||||
|
@ -56,7 +53,6 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
_lightningClientFactoryService = lightningClientFactoryService;
|
||||
_options = options;
|
||||
_storeRepository = storeRepository;
|
||||
_currencyNameTable = currencyNameTable;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
|
@ -123,7 +119,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
await SetStoreContext();
|
||||
|
||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var payoutHandler = _payoutHandlers.FindPayoutHandler(pmi);
|
||||
var payoutHandler = (LightningLikePayoutHandler) _payoutHandlers.FindPayoutHandler(pmi);
|
||||
|
||||
await using var ctx = _applicationDbContextFactory.CreateContext();
|
||||
|
||||
|
@ -132,47 +128,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(pmi.CryptoCode);
|
||||
|
||||
//we group per store and init the transfers by each
|
||||
async Task TrypayBolt(ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData, BOLT11PaymentRequest bolt11PaymentRequest)
|
||||
{
|
||||
var boltAmount = bolt11PaymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC);
|
||||
if (boltAmount != payoutBlob.CryptoAmount)
|
||||
{
|
||||
results.Add(new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = PayResult.Error,
|
||||
Message = $"The BOLT11 invoice amount ({_currencyNameTable.DisplayFormatCurrency(boltAmount, pmi.CryptoCode)}) did not match the payout's amount ({_currencyNameTable.DisplayFormatCurrency(payoutBlob.CryptoAmount.GetValueOrDefault(), pmi.CryptoCode)})",
|
||||
Destination = payoutBlob.Destination
|
||||
});
|
||||
return;
|
||||
}
|
||||
var result = await lightningClient.Pay(bolt11PaymentRequest.ToString());
|
||||
if (result.Result == PayResult.Ok)
|
||||
{
|
||||
var message = result.Details?.TotalAmount != null
|
||||
? $"Paid out {result.Details.TotalAmount.ToDecimal(LightMoneyUnit.BTC)}"
|
||||
: null;
|
||||
results.Add(new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = result.Result,
|
||||
Destination = payoutBlob.Destination,
|
||||
Message = message
|
||||
});
|
||||
payoutData.State = PayoutState.Completed;
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = result.Result,
|
||||
Destination = payoutBlob.Destination,
|
||||
Message = result.ErrorDetail
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var authorizedForInternalNode = (await _authorizationService.AuthorizeAsync(User, null, new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded;
|
||||
foreach (var payoutDatas in payouts)
|
||||
{
|
||||
|
@ -205,6 +161,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
_lightningClientFactoryService);
|
||||
foreach (var payoutData in payoutDatas)
|
||||
{
|
||||
ResultVM result;
|
||||
var blob = payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings);
|
||||
var claim = await payoutHandler.ParseClaimDestination(pmi, blob.Destination);
|
||||
try
|
||||
|
@ -212,78 +169,133 @@ namespace BTCPayServer.Data.Payouts.LightningLike
|
|||
switch (claim.destination)
|
||||
{
|
||||
case LNURLPayClaimDestinaton lnurlPayClaimDestinaton:
|
||||
var endpoint = LNURL.LNURL.Parse(lnurlPayClaimDestinaton.LNURL, out _);
|
||||
var lightningPayoutHandler = (LightningLikePayoutHandler)payoutHandler;
|
||||
var httpClient = lightningPayoutHandler.CreateClient(endpoint);
|
||||
var lnurlInfo =
|
||||
(LNURLPayRequest)await LNURL.LNURL.FetchInformation(endpoint, "payRequest",
|
||||
httpClient);
|
||||
var lm = new LightMoney(blob.CryptoAmount.Value, LightMoneyUnit.BTC);
|
||||
if (lm > lnurlInfo.MaxSendable || lm < lnurlInfo.MinSendable)
|
||||
var lnurlResult = await GetInvoiceFromLNURL(payoutData, payoutHandler, blob,
|
||||
lnurlPayClaimDestinaton, network.NBitcoinNetwork);
|
||||
if (lnurlResult.Item2 is not null)
|
||||
{
|
||||
results.Add(new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = PayResult.Error,
|
||||
Destination = blob.Destination,
|
||||
Message =
|
||||
$"The LNURL provided would not generate an invoice of {lm.MilliSatoshi}msats"
|
||||
});
|
||||
result = lnurlResult.Item2;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var lnurlPayRequestCallbackResponse =
|
||||
await lnurlInfo.SendRequest(lm, network.NBitcoinNetwork, httpClient);
|
||||
|
||||
await TrypayBolt(client, blob, payoutData, lnurlPayRequestCallbackResponse.GetPaymentRequest(network.NBitcoinNetwork));
|
||||
}
|
||||
catch (LNUrlException e)
|
||||
{
|
||||
results.Add(new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = PayResult.Error,
|
||||
Destination = blob.Destination,
|
||||
Message = e.Message
|
||||
});
|
||||
}
|
||||
result = await TrypayBolt(client, blob, payoutData, lnurlResult.Item1, pmi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case BoltInvoiceClaimDestination item1:
|
||||
await TrypayBolt(client, blob, payoutData, item1.PaymentRequest);
|
||||
result = await TrypayBolt(client, blob, payoutData, item1.PaymentRequest, pmi);
|
||||
|
||||
break;
|
||||
default:
|
||||
results.Add(new ResultVM
|
||||
result= new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = PayResult.Error,
|
||||
Destination = blob.Destination,
|
||||
Message = claim.error
|
||||
});
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
results.Add(new ResultVM
|
||||
result = new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = PayResult.Error,
|
||||
Destination = blob.Destination,
|
||||
Message = exception.Message
|
||||
});
|
||||
};
|
||||
}
|
||||
results.Add(result);
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
return View("LightningPayoutResult", results);
|
||||
}
|
||||
public static async Task<(BOLT11PaymentRequest, ResultVM)> GetInvoiceFromLNURL(PayoutData payoutData,
|
||||
LightningLikePayoutHandler handler,PayoutBlob blob, LNURLPayClaimDestinaton lnurlPayClaimDestinaton, Network network)
|
||||
{
|
||||
var endpoint = lnurlPayClaimDestinaton.LNURL.IsValidEmail()
|
||||
? LNURL.LNURL.ExtractUriFromInternetIdentifier(lnurlPayClaimDestinaton.LNURL)
|
||||
: LNURL.LNURL.Parse(lnurlPayClaimDestinaton.LNURL, out _);
|
||||
var httpClient = handler.CreateClient(endpoint);
|
||||
var lnurlInfo =
|
||||
(LNURLPayRequest)await LNURL.LNURL.FetchInformation(endpoint, "payRequest",
|
||||
httpClient);
|
||||
var lm = new LightMoney(blob.CryptoAmount.Value, LightMoneyUnit.BTC);
|
||||
if (lm > lnurlInfo.MaxSendable || lm < lnurlInfo.MinSendable)
|
||||
{
|
||||
return (null, new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = PayResult.Error,
|
||||
Destination = blob.Destination,
|
||||
Message =
|
||||
$"The LNURL provided would not generate an invoice of {lm.MilliSatoshi}msats"
|
||||
});
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var lnurlPayRequestCallbackResponse =
|
||||
await lnurlInfo.SendRequest(lm, network, httpClient);
|
||||
|
||||
return (lnurlPayRequestCallbackResponse.GetPaymentRequest(network), null);
|
||||
}
|
||||
catch (LNUrlException e)
|
||||
{
|
||||
return (null,
|
||||
new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = PayResult.Error,
|
||||
Destination = blob.Destination,
|
||||
Message = e.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static async Task<ResultVM> TrypayBolt(ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData, BOLT11PaymentRequest bolt11PaymentRequest, PaymentMethodId pmi)
|
||||
{
|
||||
var boltAmount = bolt11PaymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC);
|
||||
if (boltAmount != payoutBlob.CryptoAmount)
|
||||
{
|
||||
return new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = PayResult.Error,
|
||||
Message = $"The BOLT11 invoice amount ({boltAmount} {pmi.CryptoCode}) did not match the payout's amount ({payoutBlob.CryptoAmount.GetValueOrDefault()} {pmi.CryptoCode})",
|
||||
Destination = payoutBlob.Destination
|
||||
};
|
||||
}
|
||||
var result = await lightningClient.Pay(bolt11PaymentRequest.ToString(), new PayInvoiceParams());
|
||||
if (result.Result == PayResult.Ok)
|
||||
{
|
||||
var message = result.Details?.TotalAmount != null
|
||||
? $"Paid out {result.Details.TotalAmount.ToDecimal(LightMoneyUnit.BTC)}"
|
||||
: null;
|
||||
payoutData.State = PayoutState.Completed;
|
||||
return new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = result.Result,
|
||||
Destination = payoutBlob.Destination,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
|
||||
return new ResultVM
|
||||
{
|
||||
PayoutId = payoutData.Id,
|
||||
Result = result.Result,
|
||||
Destination = payoutBlob.Destination,
|
||||
Message = result.ErrorDetail
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private async Task SetStoreContext()
|
||||
{
|
||||
|
|
|
@ -10,9 +10,6 @@ using System.Security.Claims;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -27,20 +24,28 @@ using BTCPayServer.Services.Invoices;
|
|||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Payment;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using InvoiceCryptoInfo = BTCPayServer.Services.Invoices.InvoiceCryptoInfo;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static bool IsValidEmail(this string email)
|
||||
{
|
||||
if (string.IsNullOrEmpty(email))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return MailboxAddressValidator.TryParse(email, out var ma) && ma.ToString() == ma.Address;
|
||||
}
|
||||
|
||||
public static bool TryGetPayjoinEndpoint(this BitcoinUrlBuilder bip21, out Uri endpoint)
|
||||
{
|
||||
endpoint = bip21.UnknownParameters.TryGetValue($"{PayjoinClient.BIP21EndpointKey}", out var uri) ? new Uri(uri, UriKind.Absolute) : null;
|
||||
|
|
|
@ -80,35 +80,18 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<Au
|
|||
switch (claim.destination)
|
||||
{
|
||||
case LNURLPayClaimDestinaton lnurlPayClaimDestinaton:
|
||||
var endpoint = LNURL.LNURL.Parse(lnurlPayClaimDestinaton.LNURL, out var tag);
|
||||
var httpClient = _payoutHandler.CreateClient(endpoint);
|
||||
var lnurlInfo =
|
||||
(LNURLPayRequest)await LNURL.LNURL.FetchInformation(endpoint, "payRequest",
|
||||
httpClient);
|
||||
var lm = new LightMoney(blob.CryptoAmount.Value, LightMoneyUnit.BTC);
|
||||
if (lm > lnurlInfo.MaxSendable || lm < lnurlInfo.MinSendable)
|
||||
var lnurlResult = await UILightningLikePayoutController.GetInvoiceFromLNURL(payoutData, _payoutHandler, blob,
|
||||
lnurlPayClaimDestinaton, _network.NBitcoinNetwork);
|
||||
if (lnurlResult.Item2 is not null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var lnurlPayRequestCallbackResponse =
|
||||
await lnurlInfo.SendRequest(lm, _network.NBitcoinNetwork, httpClient);
|
||||
|
||||
if (await TrypayBolt(client, blob, payoutData,
|
||||
lnurlPayRequestCallbackResponse
|
||||
.GetPaymentRequest(_network.NBitcoinNetwork)))
|
||||
{
|
||||
ctx.Attach(payoutData);
|
||||
payoutData.State = PayoutState.Completed;
|
||||
}
|
||||
}
|
||||
catch (LNUrlException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (await TrypayBolt(client, blob, payoutData,
|
||||
lnurlResult.Item1))
|
||||
{
|
||||
ctx.Attach(payoutData);
|
||||
payoutData.State = PayoutState.Completed;
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -137,13 +120,7 @@ public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<Au
|
|||
async Task<bool> TrypayBolt(ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData,
|
||||
BOLT11PaymentRequest bolt11PaymentRequest)
|
||||
{
|
||||
var boltAmount = bolt11PaymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC);
|
||||
if (boltAmount != payoutBlob.CryptoAmount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = await lightningClient.Pay(bolt11PaymentRequest.ToString());
|
||||
return result.Result == PayResult.Ok;
|
||||
return (await UILightningLikePayoutController.TrypayBolt(lightningClient, payoutBlob, payoutData, bolt11PaymentRequest,
|
||||
payoutData.GetPaymentMethodId())).Result == PayResult.Ok;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue