From 00acbccd7f3eeed90303b805cb6f1285a29f814f Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Tue, 19 Sep 2023 02:55:15 +0200 Subject: [PATCH] Add payouts report (#5320) --- ...torePullPaymentsController.PullPayments.cs | 3 +- BTCPayServer/Data/Payouts/PayoutExtensions.cs | 16 +++ .../PullPaymentHostedService.cs | 10 ++ BTCPayServer/Hosting/BTCPayServerServices.cs | 1 + .../Reporting/PayoutsReportProvider.cs | 109 ++++++++++++++++++ .../Reporting/ProductsReportProvider.cs | 8 -- 6 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 BTCPayServer/Services/Reporting/PayoutsReportProvider.cs diff --git a/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs b/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs index 756a36415..486732dc2 100644 --- a/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs +++ b/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs @@ -538,7 +538,8 @@ namespace BTCPayServer.Controllers { var ppBlob = item.PullPayment?.GetBlob(); var payoutBlob = item.Payout.GetBlob(_jsonSerializerSettings); - string payoutSource; + item.Payout.PullPaymentData = item.PullPayment; + string payoutSource = item.Payout.GetPayoutSource(_jsonSerializerSettings); if (payoutBlob.Metadata?.TryGetValue("source", StringComparison.InvariantCultureIgnoreCase, out var source) is true) { diff --git a/BTCPayServer/Data/Payouts/PayoutExtensions.cs b/BTCPayServer/Data/Payouts/PayoutExtensions.cs index f7b0409fe..de2291e4c 100644 --- a/BTCPayServer/Data/Payouts/PayoutExtensions.cs +++ b/BTCPayServer/Data/Payouts/PayoutExtensions.cs @@ -33,6 +33,22 @@ namespace BTCPayServer.Data return PaymentMethodId.TryParse(data.PaymentMethodId, out var paymentMethodId) ? paymentMethodId : null; } + public static string GetPayoutSource(this PayoutData data, BTCPayNetworkJsonSerializerSettings jsonSerializerSettings) + { + var ppBlob = data.PullPaymentData?.GetBlob(); + var payoutBlob = data.GetBlob(jsonSerializerSettings); + string payoutSource; + if (payoutBlob.Metadata?.TryGetValue("source", StringComparison.InvariantCultureIgnoreCase, + out var source) is true) + { + return source.Value(); + } + else + { + return ppBlob?.Name ?? data.PullPaymentDataId; + } + } + public static PayoutBlob GetBlob(this PayoutData data, BTCPayNetworkJsonSerializerSettings serializers) { var result = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(data.Blob), serializers.GetSerializer(data.GetPaymentMethodId().CryptoCode)); diff --git a/BTCPayServer/HostedServices/PullPaymentHostedService.cs b/BTCPayServer/HostedServices/PullPaymentHostedService.cs index 734fc9dcc..5bbed8c25 100644 --- a/BTCPayServer/HostedServices/PullPaymentHostedService.cs +++ b/BTCPayServer/HostedServices/PullPaymentHostedService.cs @@ -157,6 +157,8 @@ namespace BTCPayServer.HostedServices public bool IncludeArchived { get; set; } public bool IncludeStoreData { get; set; } public bool IncludePullPaymentData { get; set; } + public DateTimeOffset? From { get; set; } + public DateTimeOffset? To { get; set; } } public async Task> GetPayouts(PayoutQuery payoutQuery) @@ -217,6 +219,14 @@ namespace BTCPayServer.HostedServices data.PullPaymentData == null || !data.PullPaymentData.Archived); } + if (payoutQuery.From is not null) + { + query = query.Where(data => data.Date >= payoutQuery.From); + } + if (payoutQuery.To is not null) + { + query = query.Where(data => data.Date <= payoutQuery.To); + } return await query.ToListAsync(cancellationToken); } diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 52941b3d3..7bc00c13e 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -359,6 +359,7 @@ namespace BTCPayServer.Hosting services.AddReportProvider(); services.AddReportProvider(); services.AddReportProvider(); + services.AddReportProvider(); services.AddHttpClient(WebhookSender.OnionNamedClient) .ConfigurePrimaryHttpMessageHandler(); diff --git a/BTCPayServer/Services/Reporting/PayoutsReportProvider.cs b/BTCPayServer/Services/Reporting/PayoutsReportProvider.cs new file mode 100644 index 000000000..f641d0410 --- /dev/null +++ b/BTCPayServer/Services/Reporting/PayoutsReportProvider.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Client.Models; +using BTCPayServer.Data; +using BTCPayServer.HostedServices; +using BTCPayServer.Payments; + +namespace BTCPayServer.Services.Reporting; + +public class PayoutsReportProvider:ReportProvider +{ + private readonly PullPaymentHostedService _pullPaymentHostedService; + private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings; + + public PayoutsReportProvider(PullPaymentHostedService pullPaymentHostedService, BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings) + { + _pullPaymentHostedService = pullPaymentHostedService; + _btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings; + } + + public override string Name => "Payouts"; + public override async Task Query(QueryContext queryContext, CancellationToken cancellation) + { + queryContext.ViewDefinition = CreateDefinition(); + foreach (var payout in (await _pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery() + { + Stores = new[] {queryContext.StoreId}, + From = queryContext.From, + To = queryContext.To, + IncludeArchived = true, + IncludePullPaymentData = true, + + + })).OrderBy(data => data.Date)) + { + var blob = payout.GetBlob(_btcPayNetworkJsonSerializerSettings); + var data = queryContext.CreateData(); + data.Add(payout.Date); + data.Add(payout.GetPayoutSource(_btcPayNetworkJsonSerializerSettings)); + data.Add(payout.State.ToString()); + if (PaymentMethodId.TryParse(payout.PaymentMethodId, out var paymentType)) + { + if (paymentType.PaymentType == PaymentTypes.LightningLike || paymentType.PaymentType == PaymentTypes.LNURLPay) + data.Add("Lightning"); + else if (paymentType.PaymentType == PaymentTypes.BTCLike) + data.Add("On-Chain"); + else + data.Add(paymentType.PaymentType.ToStringNormalized()); + } + else + continue; + data.Add(paymentType.CryptoCode); + data.Add(blob.CryptoAmount); + var ppBlob = payout.PullPaymentData?.GetBlob(); + data.Add(ppBlob?.Currency??paymentType.CryptoCode); + data.Add(blob.Amount); + data.Add(blob.Destination); + queryContext.Data.Add(data); + } + + + } + + private ViewDefinition CreateDefinition() + { + return new ViewDefinition() + { + Fields = new List() + { + new("Date", "datetime"), + new("Source", "string"), + new("State", "string"), + new("PaymentType", "string"), + new("Crypto", "string"), + new("CryptoAmount", "decimal"), + new("Currency", "string"), + new("CurrencyAmount", "decimal"), + new("Destination", "string") + }, + Charts = + { + new () + { + Name = "Aggregated crypto amount", + Groups = { "Crypto", "PaymentType", "State" }, + Totals = { "Crypto" }, + HasGrandTotal = false, + Aggregates = { "CryptoAmount" } + },new () + { + Name = "Aggregated amount", + Groups = { "Currency", "State" }, + Totals = { "CurrencyAmount" }, + HasGrandTotal = false, + Aggregates = { "CurrencyAmount" } + },new () + { + Name = "Aggregated amount by Source", + Groups = { "Currency", "State", "Source" }, + Totals = { "CurrencyAmount" }, + HasGrandTotal = false, + Aggregates = { "CurrencyAmount" } + } + } + }; + } +} diff --git a/BTCPayServer/Services/Reporting/ProductsReportProvider.cs b/BTCPayServer/Services/Reporting/ProductsReportProvider.cs index dd749783b..ad7548ded 100644 --- a/BTCPayServer/Services/Reporting/ProductsReportProvider.cs +++ b/BTCPayServer/Services/Reporting/ProductsReportProvider.cs @@ -1,18 +1,10 @@ -using System; using System.Linq; using System.Threading; using System.Threading.Tasks; -using BTCPayServer.Data; using BTCPayServer.Rating; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Rates; -using Dapper; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; -using Newtonsoft.Json; - namespace BTCPayServer.Services.Reporting; public class ProductsReportProvider : ReportProvider