diff --git a/BTCPayServer.Tests/FastTests.cs b/BTCPayServer.Tests/FastTests.cs index a478be9a6..9af352b66 100644 --- a/BTCPayServer.Tests/FastTests.cs +++ b/BTCPayServer.Tests/FastTests.cs @@ -513,9 +513,8 @@ namespace BTCPayServer.Tests { Assert.Single(info.Labels); var l = Assert.IsType(info.Labels["payout"]); - Assert.Equal("pullPaymentId", l.PullPaymentId); + Assert.Single(Assert.Single(l.PullPaymentPayouts, k => k.Key == "pullPaymentId").Value, "payoutId"); Assert.Equal("walletId", l.WalletId); - Assert.Equal("payoutId", l.PayoutId); } var payoutId = "payoutId"; diff --git a/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs b/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs index a90c95cc4..41f6af076 100644 --- a/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs +++ b/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs @@ -411,7 +411,10 @@ public class BitcoinLikePayoutHandler : IPayoutHandler var walletId = new WalletId(payout.StoreDataId, newTransaction.CryptoCode); _eventAggregator.Publish(new UpdateTransactionLabel(walletId, newTransaction.NewTransactionEvent.TransactionData.TransactionHash, - UpdateTransactionLabel.PayoutTemplate(payout.Id, payout.PullPaymentDataId, walletId.ToString()))); + UpdateTransactionLabel.PayoutTemplate(new () + { + {payout.PullPaymentDataId?? "", new List{payout.Id}} + }, walletId.ToString()))); } else { diff --git a/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs b/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs index d64c1681e..595453ea1 100644 --- a/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs +++ b/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs @@ -14,6 +14,7 @@ using BTCPayServer.Services.Apps; using BTCPayServer.Services.Labels; using BTCPayServer.Services.PaymentRequests; using NBitcoin; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace BTCPayServer.HostedServices @@ -75,31 +76,46 @@ namespace BTCPayServer.HostedServices var walletBlobInfo = await _walletRepository.GetWalletInfo(updateTransactionLabel.WalletId); await Task.WhenAll(updateTransactionLabel.TransactionLabels.Select(async pair => { - if (!walletTransactionsInfo.TryGetValue(pair.Key.ToString(), out var walletTransactionInfo)) + var txId = pair.Key.ToString(); + var coloredLabels = pair.Value; + if (!walletTransactionsInfo.TryGetValue(txId, out var walletTransactionInfo)) { walletTransactionInfo = new WalletTransactionInfo(); } - foreach (var label in pair.Value) + bool walletNeedUpdate = false; + foreach (var cl in coloredLabels) { - walletBlobInfo.LabelColors.TryAdd(label.label.Text, label.color); - } - - await _walletRepository.SetWalletInfo(updateTransactionLabel.WalletId, walletBlobInfo); - var update = false; - foreach (var label in pair.Value) - { - if (walletTransactionInfo.Labels.TryAdd(label.label.Text, label.label)) + if (walletBlobInfo.LabelColors.TryGetValue(cl.label.Text, out var currentColor)) { - update = true; + if (currentColor != cl.color) + { + walletNeedUpdate = true; + walletBlobInfo.LabelColors[cl.label.Text] = currentColor; + } + } + else + { + walletNeedUpdate = true; + walletBlobInfo.LabelColors.AddOrReplace(cl.label.Text, cl.color); } } - if (update) + if (walletNeedUpdate) + await _walletRepository.SetWalletInfo(updateTransactionLabel.WalletId, walletBlobInfo); + foreach (var cl in coloredLabels) { - await _walletRepository.SetWalletTransactionInfo(updateTransactionLabel.WalletId, - pair.Key.ToString(), walletTransactionInfo); + var label = cl.label; + if (walletTransactionInfo.Labels.TryGetValue(label.Text, out var existingLabel)) + { + label = label.Merge(existingLabel); + } + + walletTransactionInfo.Labels.AddOrReplace(label.Text, label); } + + await _walletRepository.SetWalletTransactionInfo(updateTransactionLabel.WalletId, + txId, walletTransactionInfo); })); } } @@ -146,12 +162,11 @@ namespace BTCPayServer.HostedServices return ("#51b13e", new ReferenceLabel("pj-exposed", invoice)); } - public static (string color, Label label) PayoutTemplate(string payoutId, string pullPaymentId, string walletId) + public static (string color, Label label) PayoutTemplate(Dictionary> pullPaymentToPayouts, string walletId) { return ("#3F88AF", new PayoutLabel() { - PayoutId = payoutId, - PullPaymentId = pullPaymentId, + PullPaymentPayouts = pullPaymentToPayouts, WalletId = walletId }); } diff --git a/BTCPayServer/PayoutProcessors/OnChain/OnChainAutomatedPayoutProcessor.cs b/BTCPayServer/PayoutProcessors/OnChain/OnChainAutomatedPayoutProcessor.cs index bdae81016..42bedfc36 100644 --- a/BTCPayServer/PayoutProcessors/OnChain/OnChainAutomatedPayoutProcessor.cs +++ b/BTCPayServer/PayoutProcessors/OnChain/OnChainAutomatedPayoutProcessor.cs @@ -179,8 +179,10 @@ namespace BTCPayServer.PayoutProcessors.OnChain { _eventAggregator.Publish(new UpdateTransactionLabel(walletId, txHash, - UpdateTransactionLabel.PayoutTemplate(payoutData.Id, payoutData.PullPaymentDataId, - walletId.ToString()))); + UpdateTransactionLabel.PayoutTemplate(new () + { + {payoutData.PullPaymentDataId?? "", new List{payoutData.Id}} + }, walletId.ToString()))); } await Task.WhenAny(tcs.Task, task); } diff --git a/BTCPayServer/Services/Labels/Label.cs b/BTCPayServer/Services/Labels/Label.cs index 216e3c6c2..563e210ef 100644 --- a/BTCPayServer/Services/Labels/Label.cs +++ b/BTCPayServer/Services/Labels/Label.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.Linq; using BTCPayServer.Client.Models; +using NBitcoin; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -8,6 +11,11 @@ namespace BTCPayServer.Services.Labels public abstract class Label : LabelData { + public virtual Label Merge(LabelData other) + { + return this; + } + static void FixLegacy(JObject jObj, ReferenceLabel refLabel) { if (refLabel.Reference is null && jObj.ContainsKey("id")) @@ -16,8 +24,11 @@ namespace BTCPayServer.Services.Labels } static void FixLegacy(JObject jObj, PayoutLabel payoutLabel) { - if (payoutLabel.PayoutId is null) - payoutLabel.PayoutId = jObj["id"].Value(); + if (jObj.ContainsKey("id") && payoutLabel.PullPaymentPayouts.Count is 0) + { + var pullPaymentId = jObj["pullPaymentId"]?.Value() ?? string.Empty; + payoutLabel.PullPaymentPayouts.Add(pullPaymentId, new List() { jObj["id"].Value() }); + } FixLegacy(jObj, (Label)payoutLabel); } static void FixLegacy(JObject jObj, Label label) @@ -111,8 +122,23 @@ namespace BTCPayServer.Services.Labels Type = "payout"; Text = "payout"; } - public string PayoutId { get; set; } + + public Dictionary> PullPaymentPayouts { get; set; } = new(); public string WalletId { get; set; } - public string PullPaymentId { get; set; } + + public override Label Merge(LabelData other) + { + if (other is not PayoutLabel otherPayoutLabel) return base.Merge(other); + foreach (var pullPaymentPayout in otherPayoutLabel.PullPaymentPayouts) + { + if (!PullPaymentPayouts.TryGetValue(pullPaymentPayout.Key, out var pullPaymentPayouts)) + { + pullPaymentPayouts = new List(); + PullPaymentPayouts.Add(pullPaymentPayout.Key, pullPaymentPayouts); + } + pullPaymentPayouts.AddRange(pullPaymentPayout.Value); + } + return base.Merge(other); + } } } diff --git a/BTCPayServer/Services/Labels/LabelFactory.cs b/BTCPayServer/Services/Labels/LabelFactory.cs index 58ce14800..c28ca6ceb 100644 --- a/BTCPayServer/Services/Labels/LabelFactory.cs +++ b/BTCPayServer/Services/Labels/LabelFactory.cs @@ -3,6 +3,7 @@ using System; using System.Drawing; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -57,6 +58,15 @@ namespace BTCPayServer.Services.Labels Color = color, TextColor = TextColor(color) }; + + string PayoutLabelText(KeyValuePair> pair) + { + if (pair.Value.Count == 1) + return $"Paid a payout of a pull payment ({pair.Key})"; + else + return $"Paid payouts of a pull payment ({pair.Key})"; + } + if (uncoloredLabel is ReferenceLabel refLabel) { var refInLabel = string.IsNullOrEmpty(refLabel.Reference) ? string.Empty : $"({refLabel.Reference})"; @@ -90,13 +100,15 @@ namespace BTCPayServer.Services.Labels } else if (uncoloredLabel is PayoutLabel payoutLabel) { - coloredLabel.Tooltip = - $"Paid a payout{(payoutLabel.PullPaymentId is null ? string.Empty : $" of a pull payment ({payoutLabel.PullPaymentId})")}"; + coloredLabel.Tooltip = payoutLabel.PullPaymentPayouts.Count > 1 + ? $"
    {string.Join(string.Empty, payoutLabel.PullPaymentPayouts.Select(pair => $"
  • {PayoutLabelText(pair)}
  • "))}
" + : payoutLabel.PullPaymentPayouts.Select(PayoutLabelText).ToString(); + coloredLabel.Link = string.IsNullOrEmpty(payoutLabel.WalletId) ? null - : _linkGenerator.PayoutLink(payoutLabel.WalletId, - payoutLabel.PullPaymentId, PayoutState.Completed, request.Scheme, request.Host, + : _linkGenerator.PayoutLink(payoutLabel.WalletId, null, PayoutState.Completed, request.Scheme, request.Host, request.PathBase); + } return coloredLabel; } diff --git a/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml b/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml index b444d7d3c..8b431f404 100644 --- a/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml +++ b/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml @@ -17,7 +17,12 @@ max-width: 200px; } } - + /* pull actions area, so that it is besides the search form */ + @@media (min-width: 1200px) { + #Actions { + margin-top: -4rem; + } + } .unconf > * { opacity: 0.5; } @@ -49,11 +54,14 @@ border: 0; } - /* pull actions area, so that it is besides the search form */ - @@media (min-width: 1200px) { - #Actions { - margin-top: -4rem; - } + .label-tooltip .tooltip-inner { + max-width: 15rem; + text-align: left; + } + + .label-tooltip ul { + margin: 0; + padding-left: var(--btcpay-space-m); } } @@ -161,7 +169,9 @@