diff --git a/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs b/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs index 96febae70..84dd31037 100644 --- a/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs @@ -1,7 +1,9 @@ using System; +using System.Linq; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; +using BTCPayServer.Payments; using BTCPayServer.Services.Rates; namespace BTCPayServer.Models.AppViewModels @@ -50,6 +52,20 @@ namespace BTCPayServer.Models.AppViewModels public DateTime? LastResetDate { get; set; } public DateTime? NextResetDate { get; set; } } + public class Contribution + { + public PaymentMethodId PaymentMehtodId { get; set; } + public decimal Value { get; set; } + public decimal CurrencyValue { get; set; } + } + public class Contributions : Dictionary + { + public Contributions(IEnumerable> collection) : base(collection) + { + TotalCurrency = Values.Select(v => v.CurrencyValue).Sum(); + } + public decimal TotalCurrency { get; } + } public bool Started => !StartDate.HasValue || DateTime.Now.ToUniversalTime() > StartDate; diff --git a/BTCPayServer/PaymentRequest/PaymentRequestService.cs b/BTCPayServer/PaymentRequest/PaymentRequestService.cs index 0dc956a73..65cd8058c 100644 --- a/BTCPayServer/PaymentRequest/PaymentRequestService.cs +++ b/BTCPayServer/PaymentRequest/PaymentRequestService.cs @@ -51,10 +51,8 @@ namespace BTCPayServer.PaymentRequest { var rateRules = pr.StoreData.GetStoreBlob().GetRateRules(_BtcPayNetworkProvider); var invoices = await _PaymentRequestRepository.GetInvoicesForPaymentRequest(pr.Id); - var paymentStats = _AppService.GetCurrentContributionAmountStats(invoices, true); - var amountCollected = - await _AppService.GetCurrentContributionAmount(paymentStats, blob.Currency, rateRules); - if (amountCollected >= blob.Amount) + var contributions = _AppService.GetContributionsByPaymentMethodId(invoices, true); + if (contributions.TotalCurrency >= blob.Amount) { currentStatus = PaymentRequestData.PaymentRequestStatus.Completed; } @@ -80,17 +78,14 @@ namespace BTCPayServer.PaymentRequest var invoices = await _PaymentRequestRepository.GetInvoicesForPaymentRequest(id); - var paymentStats = _AppService.GetCurrentContributionAmountStats(invoices, true); - var amountCollected = - await _AppService.GetCurrentContributionAmount(paymentStats, blob.Currency, rateRules); - - var amountDue = blob.Amount - amountCollected; + var paymentStats = _AppService.GetContributionsByPaymentMethodId(invoices, true); + var amountDue = blob.Amount - paymentStats.TotalCurrency; return new ViewPaymentRequestViewModel(pr) { AmountFormatted = _currencies.FormatCurrency(blob.Amount, blob.Currency), - AmountCollected = amountCollected, - AmountCollectedFormatted = _currencies.FormatCurrency(amountCollected, blob.Currency), + AmountCollected = paymentStats.TotalCurrency, + AmountCollectedFormatted = _currencies.FormatCurrency(paymentStats.TotalCurrency, blob.Currency), AmountDue = amountDue, AmountDueFormatted = _currencies.FormatCurrency(amountDue, blob.Currency), CurrencyData = _currencies.GetCurrencyData(blob.Currency, true), diff --git a/BTCPayServer/Services/Apps/AppService.cs b/BTCPayServer/Services/Apps/AppService.cs index 89d383eec..68e1cf864 100644 --- a/BTCPayServer/Services/Apps/AppService.cs +++ b/BTCPayServer/Services/Apps/AppService.cs @@ -25,6 +25,7 @@ using Microsoft.EntityFrameworkCore; using NBitpayClient; using YamlDotNet.RepresentationModel; using static BTCPayServer.Controllers.AppsController; +using static BTCPayServer.Models.AppViewModels.ViewCrowdfundViewModel; namespace BTCPayServer.Services.Apps { @@ -33,7 +34,6 @@ namespace BTCPayServer.Services.Apps ApplicationDbContextFactory _ContextFactory; private readonly InvoiceRepository _InvoiceRepository; CurrencyNameTable _Currencies; - private readonly RateFetcher _RateFetcher; private readonly HtmlSanitizer _HtmlSanitizer; private readonly BTCPayNetworkProvider _Networks; public CurrencyNameTable Currencies => _Currencies; @@ -41,13 +41,11 @@ namespace BTCPayServer.Services.Apps InvoiceRepository invoiceRepository, BTCPayNetworkProvider networks, CurrencyNameTable currencies, - RateFetcher rateFetcher, HtmlSanitizer htmlSanitizer) { _ContextFactory = contextFactory; _InvoiceRepository = invoiceRepository; _Currencies = currencies; - _RateFetcher = rateFetcher; _HtmlSanitizer = htmlSanitizer; _Networks = networks; } @@ -94,17 +92,12 @@ namespace BTCPayServer.Services.Apps var completeInvoices = invoices.Where(entity => entity.Status == InvoiceStatus.Complete || entity.Status == InvoiceStatus.Confirmed).ToArray(); var pendingInvoices = invoices.Where(entity => !(entity.Status == InvoiceStatus.Complete || entity.Status == InvoiceStatus.Confirmed)).ToArray(); + + var rateRules = appData.StoreData.GetStoreBlob().GetRateRules(_Networks); - var pendingPaymentStats = GetCurrentContributionAmountStats(pendingInvoices, !settings.EnforceTargetAmount); - var paymentStats = GetCurrentContributionAmountStats(completeInvoices, !settings.EnforceTargetAmount); - - var currentAmount = await GetCurrentContributionAmount( - paymentStats, - settings.TargetCurrency, rateRules); - var currentPendingAmount = await GetCurrentContributionAmount( - pendingPaymentStats, - settings.TargetCurrency, rateRules); + var pendingPayments = GetContributionsByPaymentMethodId(pendingInvoices, !settings.EnforceTargetAmount); + var currentPayments = GetContributionsByPaymentMethodId(completeInvoices, !settings.EnforceTargetAmount); var perkCount = invoices .Where(entity => !string.IsNullOrEmpty(entity.ProductInformation.ItemCode)) @@ -153,13 +146,11 @@ namespace BTCPayServer.Services.Apps Info = new ViewCrowdfundViewModel.CrowdfundInfo() { TotalContributors = invoices.Length, - CurrentPendingAmount = currentPendingAmount, - CurrentAmount = currentAmount, - ProgressPercentage = (currentAmount / settings.TargetAmount) * 100, - PendingProgressPercentage = (currentPendingAmount / settings.TargetAmount) * 100, + ProgressPercentage = (currentPayments.TotalCurrency / settings.TargetAmount) * 100, + PendingProgressPercentage = (pendingPayments.TotalCurrency / settings.TargetAmount) * 100, LastUpdated = DateTime.Now, - PaymentStats = paymentStats, - PendingPaymentStats = pendingPaymentStats, + PaymentStats = currentPayments.ToDictionary(c => c.Key.ToString(), c => c.Value.Value), + PendingPaymentStats = pendingPayments.ToDictionary(c => c.Key.ToString(), c => c.Value.Value), LastResetDate = lastResetDate, NextResetDate = nextResetDate } @@ -289,46 +280,19 @@ namespace BTCPayServer.Services.Apps .ToArray(); } - public async Task GetCurrentContributionAmount(Dictionary stats, string primaryCurrency, RateRules rateRules) + public Contributions GetContributionsByPaymentMethodId(InvoiceEntity[] invoices, bool softcap) { - var result = new List(); - - var ratesTask = _RateFetcher.FetchRates( - stats.Keys - .Select((x) => new CurrencyPair(primaryCurrency, PaymentMethodId.Parse(x).CryptoCode)) - .Distinct() - .ToHashSet(), - rateRules).Select(async rateTask => - { - var (key, value) = rateTask; - var tResult = await value; - var rate = tResult.BidAsk?.Bid; - if (rate == null) - return; - - foreach (var stat in stats) - { - if (string.Equals(PaymentMethodId.Parse(stat.Key).CryptoCode, key.Right, - StringComparison.InvariantCultureIgnoreCase)) - { - result.Add((1m / rate.Value) * stat.Value); - } - } - }); - - await Task.WhenAll(ratesTask); - - return result.Sum(); - } - - public Dictionary GetCurrentContributionAmountStats(InvoiceEntity[] invoices, bool softcap) - { - return invoices + var contributions = invoices .SelectMany(p => { + var contribution = new Contribution(); + contribution.PaymentMehtodId = new PaymentMethodId(p.ProductInformation.Currency, PaymentTypes.BTCLike); + contribution.CurrencyValue = p.ProductInformation.Price; + contribution.Value = contribution.CurrencyValue; + // For hardcap, we count newly created invoices as part of the contributions if (!softcap && p.Status == InvoiceStatus.New) - return new[] { (Key: p.ProductInformation.Currency, Value: p.ProductInformation.Price) }; + return new[] { contribution }; // If the user get a donation via other mean, he can register an invoice manually for such amount // then mark the invoice as complete @@ -336,20 +300,38 @@ namespace BTCPayServer.Services.Apps if (payments.Count == 0 && p.ExceptionStatus == InvoiceExceptionStatus.Marked && p.Status == InvoiceStatus.Complete) - return new[] { (Key: p.ProductInformation.Currency, Value: p.ProductInformation.Price) }; + return new[] { contribution }; + + contribution.CurrencyValue = 0m; + contribution.Value = 0m; // If an invoice has been marked invalid, remove the contribution if (p.ExceptionStatus == InvoiceExceptionStatus.Marked && p.Status == InvoiceStatus.Invalid) - return new[] { (Key: p.ProductInformation.Currency, Value: 0m) }; + return new[] { contribution }; + // Else, we just sum the payments return payments - .Select(pay => (Key: pay.GetPaymentMethodId().ToString(), Value: pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee)) + .Select(pay => + { + var paymentMethodContribution = new Contribution(); + paymentMethodContribution.PaymentMehtodId = pay.GetPaymentMethodId(); + paymentMethodContribution.Value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee; + var rate = p.GetPaymentMethod(paymentMethodContribution.PaymentMehtodId, _Networks).Rate; + paymentMethodContribution.CurrencyValue = rate * paymentMethodContribution.Value; + return paymentMethodContribution; + }) .ToArray(); }) - .GroupBy(p => p.Key) - .ToDictionary(p => p.Key, p => p.Select(v => v.Value).Sum()); + .GroupBy(p => p.PaymentMehtodId) + .ToDictionary(p => p.Key, p => new Contribution() + { + PaymentMehtodId = p.Key, + Value = p.Select(v => v.Value).Sum(), + CurrencyValue = p.Select(v => v.Value).Sum() + }); + return new Contributions(contributions); } private class PosHolder