btcpayserver/BTCPayServer/PayoutProcessors/Lightning/LightningAutomatedPayoutProcessor.cs
Nicolas Dorier a295e123bc
Migrate Payouts to new format (#5989)
* Migrate Payouts to new format

* Rename PayoutData column to PayoutMethodId
2024-06-28 20:07:53 +09:00

151 lines
6.7 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.Data.Payouts.LightningLike;
using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payouts;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NBitcoin;
using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest;
using PayoutData = BTCPayServer.Data.PayoutData;
using PayoutProcessorData = BTCPayServer.Data.PayoutProcessorData;
namespace BTCPayServer.PayoutProcessors.Lightning;
public class LightningAutomatedPayoutProcessor : BaseAutomatedPayoutProcessor<LightningAutomatedPayoutBlob>
{
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
private readonly LightningClientFactoryService _lightningClientFactoryService;
private readonly UserService _userService;
private readonly IOptions<LightningNetworkOptions> _options;
private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly LightningLikePayoutHandler _payoutHandler;
public BTCPayNetwork Network => _payoutHandler.Network;
private readonly PaymentMethodHandlerDictionary _handlers;
public LightningAutomatedPayoutProcessor(
PayoutMethodId payoutMethodId,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
LightningClientFactoryService lightningClientFactoryService,
PayoutMethodHandlerDictionary payoutHandlers,
UserService userService,
ILoggerFactory logger, IOptions<LightningNetworkOptions> options,
StoreRepository storeRepository, PayoutProcessorData payoutProcessorSettings,
ApplicationDbContextFactory applicationDbContextFactory,
PaymentMethodHandlerDictionary handlers,
IPluginHookService pluginHookService,
EventAggregator eventAggregator,
PullPaymentHostedService pullPaymentHostedService) :
base(PaymentTypes.LN.GetPaymentMethodId(GetPayoutHandler(payoutHandlers, payoutMethodId).Network.CryptoCode), logger, storeRepository, payoutProcessorSettings, applicationDbContextFactory,
handlers, pluginHookService, eventAggregator)
{
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
_lightningClientFactoryService = lightningClientFactoryService;
_userService = userService;
_options = options;
_pullPaymentHostedService = pullPaymentHostedService;
_payoutHandler = GetPayoutHandler(payoutHandlers, payoutMethodId);
_handlers = handlers;
}
private static LightningLikePayoutHandler GetPayoutHandler(PayoutMethodHandlerDictionary payoutHandlers, PayoutMethodId payoutMethodId)
{
return (LightningLikePayoutHandler)payoutHandlers[payoutMethodId];
}
private async Task HandlePayout(PayoutData payoutData, ILightningClient lightningClient)
{
if (payoutData.State != PayoutState.AwaitingPayment)
return;
var res = await _pullPaymentHostedService.MarkPaid(new MarkPayoutRequest()
{
State = PayoutState.InProgress, PayoutId = payoutData.Id, Proof = null
});
if (res != MarkPayoutRequest.PayoutPaidResult.Ok)
{
return;
}
var blob = payoutData.GetBlob(_btcPayNetworkJsonSerializerSettings);
var claim = await _payoutHandler.ParseClaimDestination(blob.Destination, CancellationToken);
try
{
switch (claim.destination)
{
case LNURLPayClaimDestinaton lnurlPayClaimDestinaton:
var lnurlResult = await UILightningLikePayoutController.GetInvoiceFromLNURL(payoutData,
_payoutHandler, blob,
lnurlPayClaimDestinaton, Network.NBitcoinNetwork, CancellationToken);
if (lnurlResult.Item2 is null)
{
await TrypayBolt(lightningClient, blob, payoutData,
lnurlResult.Item1);
}
break;
case BoltInvoiceClaimDestination item1:
await TrypayBolt(lightningClient, blob, payoutData, item1.PaymentRequest);
break;
}
}
catch (Exception e)
{
Logs.PayServer.LogError(e, $"Could not process payout {payoutData.Id}");
}
if (payoutData.State != PayoutState.InProgress || payoutData.Proof is not null)
{
await _pullPaymentHostedService.MarkPaid(new MarkPayoutRequest()
{
State = payoutData.State, PayoutId = payoutData.Id, Proof = payoutData.GetProofBlobJson()
});
}
}
protected override async Task<bool> ProcessShouldSave(object paymentMethodConfig, List<PayoutData> payouts)
{
var processorBlob = GetBlob(PayoutProcessorSettings);
var lightningSupportedPaymentMethod = (LightningPaymentMethodConfig)paymentMethodConfig;
if (lightningSupportedPaymentMethod.IsInternalNode &&
!(await Task.WhenAll((await _storeRepository.GetStoreUsers(PayoutProcessorSettings.StoreId))
.Where(user =>
user.StoreRole.ToPermissionSet(PayoutProcessorSettings.StoreId)
.Contains(Policies.CanModifyStoreSettings, PayoutProcessorSettings.StoreId))
.Select(user => user.Id)
.Select(s => _userService.IsAdminUser(s)))).Any(b => b))
{
return false;
}
var client =
lightningSupportedPaymentMethod.CreateLightningClient(Network, _options.Value,
_lightningClientFactoryService);
await Task.WhenAll(payouts.Select(data => HandlePayout(data, client)));
//we return false because this processor handles db updates on its own
return false;
}
//we group per store and init the transfers by each
async Task<bool> TrypayBolt(ILightningClient lightningClient, PayoutBlob payoutBlob, PayoutData payoutData,
BOLT11PaymentRequest bolt11PaymentRequest)
{
return (await UILightningLikePayoutController.TrypayBolt(lightningClient, payoutBlob, payoutData,
bolt11PaymentRequest,
CancellationToken)).Result is PayResult.Ok ;
}
}