diff --git a/Plugins/BTCPayServer.Plugins.Prism/Components/PrismBalances.razor b/Plugins/BTCPayServer.Plugins.Prism/Components/PrismBalances.razor
index 983f81c..35a1ea5 100644
--- a/Plugins/BTCPayServer.Plugins.Prism/Components/PrismBalances.razor
+++ b/Plugins/BTCPayServer.Plugins.Prism/Components/PrismBalances.razor
@@ -43,6 +43,7 @@
| Payout Id |
Reserve fee |
Amount |
+ Actions |
@foreach (var (payoutId, pendingPayout) in PendingPayouts)
{
@@ -50,6 +51,8 @@
@payoutId |
@pendingPayout.FeeCharged |
@pendingPayout.PayoutAmount |
+
+ |
}
@@ -70,6 +73,9 @@
[Parameter]
public EventCallback<(string destination, long newBalance)> OnUpdate { get; set; }
+
+ [Parameter]
+ public EventCallback OnCancelPayout { get; set; }
private EventCallback StartUpdate(string dest, long balance)
{
@@ -86,6 +92,5 @@
await OnUpdate.InvokeAsync((UpdatingDestination, Convert.ToInt64(UpdatingValue.Value * 1000m)));
UpdatingDestination = null;
}
-
}
\ No newline at end of file
diff --git a/Plugins/BTCPayServer.Plugins.Prism/Components/PrismEdit.razor b/Plugins/BTCPayServer.Plugins.Prism/Components/PrismEdit.razor
index 99d1e1a..8911a76 100644
--- a/Plugins/BTCPayServer.Plugins.Prism/Components/PrismEdit.razor
+++ b/Plugins/BTCPayServer.Plugins.Prism/Components/PrismEdit.razor
@@ -1,6 +1,7 @@
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client.Models
+@using BTCPayServer.HostedServices
@using BTCPayServer.Payments
@using BTCPayServer.PayoutProcessors
@using Microsoft.AspNetCore.Http
@@ -14,6 +15,7 @@
@inject IEnumerable PayoutProcessorFactories
@inject SatBreaker SatBreaker
@inject LinkGenerator LinkGenerator
+@inject PullPaymentHostedService PullPaymentHostedService
@inject IHttpContextAccessor HttpContextAccessor
@inject ILogger Logger
@implements IDisposable
@@ -134,7 +136,13 @@ else
}
-
+
@if (StatusMessageModel != null)
@@ -467,6 +475,10 @@ else
else
{
+ if (string.IsNullOrEmpty(SelectedDestinationId))
+ {
+ SelectedDestinationId = obj.Id;
+ }
Settings.Destinations.AddOrReplace(SelectedDestinationId, obj.Destination);
await SaveDestinations();
var needSave = false;
@@ -509,4 +521,10 @@ else
}
+ private async Task CancelPayout(string payoutId)
+ {
+ await PullPaymentHostedService.Cancel(
+ new PullPaymentHostedService.CancelRequest(new[] {payoutId}, new[] {StoreId}));
+ }
+
}
\ No newline at end of file
diff --git a/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs b/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs
index 68cd046..38a8250 100644
--- a/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs
+++ b/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs
@@ -77,15 +77,24 @@ namespace BTCPayServer.Plugins.Prism
}
await base.StartAsync(cancellationToken);
- _ = CheckPayouts(CancellationToken);
+ PushEvent(new CheckPayoutsEvt());
}
protected override void SubscribeToEvents()
{
base.SubscribeToEvents();
Subscribe();
+ Subscribe();
+ Subscribe();
}
+ class CheckPayoutsEvt
+ {
+
+ }
+
+ private TaskCompletionSource _checkPayoutTcs = new();
+
///
/// Go through generated payouts and check if they are completed or cancelled, and then remove them from the list.
/// If the fee can be fetched, we compute what the difference was from the original fee we computed (hardcoded 2% of the balance)
@@ -94,117 +103,117 @@ namespace BTCPayServer.Plugins.Prism
///
private async Task CheckPayouts(CancellationToken cancellationToken)
{
- while (!cancellationToken.IsCancellationRequested)
+ try
{
- try
+ _checkPayoutTcs = new TaskCompletionSource();
+ var payoutsToCheck =
+ _prismSettings.ToDictionary(pair => pair.Key, pair => pair.Value.PendingPayouts);
+ var payoutIds = payoutsToCheck
+ .SelectMany(pair => pair.Value?.Keys.ToArray() ?? Array.Empty()).ToArray();
+ var payouts = (await _pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
{
- var payoutsToCheck =
- _prismSettings.ToDictionary(pair => pair.Key, pair => pair.Value.PendingPayouts);
- var payoutIds = payoutsToCheck
- .SelectMany(pair => pair.Value?.Keys.ToArray() ?? Array.Empty()).ToArray();
- var payouts = (await _pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
- {
- PayoutIds = payoutIds,
- States = new[] {PayoutState.Cancelled, PayoutState.Completed}
- }));
- var lnClients = new Dictionary();
- var res = new Dictionary();
+ PayoutIds = payoutIds,
+ States = new[] {PayoutState.Cancelled, PayoutState.Completed}
+ }));
+ var lnClients = new Dictionary();
+ var res = new Dictionary();
- foreach (var payout in payouts)
+ foreach (var payout in payouts)
+ {
+ if (payoutsToCheck.TryGetValue(payout.StoreDataId, out var pendingPayouts) &&
+ pendingPayouts.TryGetValue(payout.Id, out var pendingPayout))
{
- if (payoutsToCheck.TryGetValue(payout.StoreDataId, out var pendingPayouts) &&
- pendingPayouts.TryGetValue(payout.Id, out var pendingPayout))
+ long toCredit = 0;
+ switch (payout.State)
{
- long toCredit = 0;
- switch (payout.State)
- {
- case PayoutState.Completed:
+ case PayoutState.Completed:
- var proof = _lightningLikePayoutHandler.ParseProof(payout) as PayoutLightningBlob;
+ var proof = _lightningLikePayoutHandler.ParseProof(payout) as PayoutLightningBlob;
- long? feePaid = null;
- if (!string.IsNullOrEmpty(proof?.PaymentHash))
+ long? feePaid = null;
+ if (!string.IsNullOrEmpty(proof?.PaymentHash))
+ {
+ if (!lnClients.TryGetValue(payout.StoreDataId, out var lnClient))
{
- if (!lnClients.TryGetValue(payout.StoreDataId, out var lnClient))
+ var store = await _storeRepository.FindStore(payout.StoreDataId);
+
+ var network = _btcPayNetworkProvider.GetNetwork("BTC");
+ var id = new PaymentMethodId("BTC", LightningPaymentType.Instance);
+ var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
+ .OfType()
+ .FirstOrDefault(d => d.PaymentId == id);
+ if (existing?.GetExternalLightningUrl() is { } connectionString)
{
- var store = await _storeRepository.FindStore(payout.StoreDataId);
-
- var network = _btcPayNetworkProvider.GetNetwork("BTC");
- var id = new PaymentMethodId("BTC", LightningPaymentType.Instance);
- var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
- .OfType()
- .FirstOrDefault(d => d.PaymentId == id);
- if (existing?.GetExternalLightningUrl() is { } connectionString)
- {
- lnClient = _lightningClientFactoryService.Create(connectionString,
- network);
- }
- else if (existing?.IsInternalNode is true &&
- _lightningNetworkOptions.Value.InternalLightningByCryptoCode
- .TryGetValue(network.CryptoCode,
- out var internalLightningNode))
- {
- lnClient = _lightningClientFactoryService.Create(internalLightningNode,
- network);
- }
-
-
- lnClients.Add(payout.StoreDataId, lnClient);
+ lnClient = _lightningClientFactoryService.Create(connectionString,
+ network);
+ }
+ else if (existing?.IsInternalNode is true &&
+ _lightningNetworkOptions.Value.InternalLightningByCryptoCode
+ .TryGetValue(network.CryptoCode,
+ out var internalLightningNode))
+ {
+ lnClient = _lightningClientFactoryService.Create(internalLightningNode,
+ network);
}
- if (lnClient is not null && proof?.PaymentHash is not null)
- {
- try
- {
- var p = await lnClient.GetPayment(proof.PaymentHash, CancellationToken);
- feePaid = (long) p?.Fee?.ToUnit(LightMoneyUnit.Satoshi);
- }
- catch (Exception e)
- {
- _logger.LogError(e,
- "The payment fee could not be fetched from the lightning node due to an error, so we will use the allocated 2% as the fee.");
- }
+ lnClients.Add(payout.StoreDataId, lnClient);
+ }
+
+ if (lnClient is not null && proof?.PaymentHash is not null)
+ {
+ try
+ {
+
+ var p = await lnClient.GetPayment(proof.PaymentHash, CancellationToken);
+ feePaid = (long) p?.Fee?.ToUnit(LightMoneyUnit.Satoshi);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e,
+ "The payment fee could not be fetched from the lightning node due to an error, so we will use the allocated 2% as the fee.");
}
}
+ }
- if (feePaid is not null)
- {
- toCredit = pendingPayout.FeeCharged - feePaid.Value;
- }
+ if (feePaid is not null)
+ {
+ toCredit = pendingPayout.FeeCharged - feePaid.Value;
+ }
- break;
- case PayoutState.Cancelled:
- toCredit = pendingPayout.PayoutAmount + pendingPayout.FeeCharged;
- break;
- }
-
- res.TryAdd(payout.StoreDataId,
- new CreditDestination(payout.StoreDataId, new Dictionary(),
- new List()));
- var credDest = res[payout.StoreDataId];
- credDest.PayoutsToRemove.Add(payout.Id);
-
- credDest.Destcredits.Add(payout.GetBlob(_btcPayNetworkJsonSerializerSettings).Destination,
- toCredit);
+ break;
+ case PayoutState.Cancelled:
+ toCredit = pendingPayout.PayoutAmount + pendingPayout.FeeCharged;
+ break;
}
+
+ res.TryAdd(payout.StoreDataId,
+ new CreditDestination(payout.StoreDataId, new Dictionary(),
+ new List()));
+ var credDest = res[payout.StoreDataId];
+ credDest.PayoutsToRemove.Add(payout.Id);
+
+ credDest.Destcredits.Add(payout.GetBlob(_btcPayNetworkJsonSerializerSettings).Destination,
+ toCredit);
}
-
- var tcs = new TaskCompletionSource(cancellationToken);
- PushEvent(new PayoutCheckResult(res.Values.ToArray(), tcs));
- //we wait for ProcessEvent to handle this result so that we avoid race conditions.
- await tcs.Task;
- }
- catch (Exception e)
- {
- _logger.LogError(e, "Error while checking payouts");
}
- await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
+ PushEvent(new PayoutCheckResult(res.Values.ToArray()));
+
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Error while checking payouts");
}
- }
- record PayoutCheckResult(CreditDestination[] CreditDestinations, TaskCompletionSource Tcs);
+ _ = Task.WhenAny(_checkPayoutTcs.Task, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken)).ContinueWith(
+ task =>
+ {
+ if(!task.IsCanceled)
+ PushEvent(new CheckPayoutsEvt());
+ }, cancellationToken);
+ }
+ record PayoutCheckResult(CreditDestination[] CreditDestinations);
record CreditDestination(string StoreId, Dictionary Destcredits, List PayoutsToRemove);
@@ -298,8 +307,6 @@ namespace BTCPayServer.Plugins.Prism
}
}
}
-
- payoutCheckResult.Tcs.SetResult();
return;
}
@@ -379,6 +386,19 @@ namespace BTCPayServer.Plugins.Prism
await UpdatePrismSettingsForStore(invoiceEvent.Invoice.StoreId, prismSettings, true);
}
}
+
+ if(evt is CheckPayoutsEvt)
+ {
+ await CheckPayouts(cancellationToken);
+ }
+ if(evt is PayoutEvent payoutEvent)
+ {
+ if (!_prismSettings.TryGetValue(payoutEvent.Payout.StoreDataId, out var prismSettings))
+ {
+ return;
+ }
+ //TODO: Trigger checking payouts, but we have to make sure we dont end up calling it multiple times at the same time as they schedule recursively.
+ }
}
catch (Exception e)
{
@@ -390,6 +410,7 @@ namespace BTCPayServer.Plugins.Prism
}
}
+
private async Task CreatePayouts(string storeId, PrismSettings prismSettings)
{
var result = false;