mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
Fix: Incorrect calculation for crowdfund and payment request status (#6381)
This commit is contained in:
parent
fdbee350b8
commit
12d4803c5c
@ -66,12 +66,10 @@ namespace BTCPayServer.PaymentRequest
|
||||
{
|
||||
var invoices = await _paymentRequestRepository.GetInvoicesForPaymentRequest(pr.Id);
|
||||
var contributions = _invoiceRepository.GetContributionsByPaymentMethodId(blob.Currency, invoices, true);
|
||||
var allSettled = contributions.All(i => i.Value.Settled);
|
||||
var isPaid = contributions.TotalCurrency >= blob.Amount;
|
||||
|
||||
if (isPaid)
|
||||
if (contributions.Total >= blob.Amount)
|
||||
{
|
||||
currentStatus = allSettled
|
||||
currentStatus = contributions.TotalSettled >= blob.Amount
|
||||
? Client.Models.PaymentRequestData.PaymentRequestStatus.Completed
|
||||
: Client.Models.PaymentRequestData.PaymentRequestStatus.Processing;
|
||||
}
|
||||
@ -99,7 +97,7 @@ namespace BTCPayServer.PaymentRequest
|
||||
var blob = pr.GetBlob();
|
||||
var invoices = await _paymentRequestRepository.GetInvoicesForPaymentRequest(id);
|
||||
var paymentStats = _invoiceRepository.GetContributionsByPaymentMethodId(blob.Currency, invoices, true);
|
||||
var amountDue = blob.Amount - paymentStats.TotalCurrency;
|
||||
var amountDue = blob.Amount - paymentStats.Total;
|
||||
var pendingInvoice = invoices.OrderByDescending(entity => entity.InvoiceTime)
|
||||
.FirstOrDefault(entity => entity.Status == InvoiceStatus.New);
|
||||
|
||||
@ -107,8 +105,8 @@ namespace BTCPayServer.PaymentRequest
|
||||
{
|
||||
Archived = pr.Archived,
|
||||
AmountFormatted = _displayFormatter.Currency(blob.Amount, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||
AmountCollected = paymentStats.TotalCurrency,
|
||||
AmountCollectedFormatted = _displayFormatter.Currency(paymentStats.TotalCurrency, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||
AmountCollected = paymentStats.Total,
|
||||
AmountCollectedFormatted = _displayFormatter.Currency(paymentStats.Total, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||
AmountDue = amountDue,
|
||||
AmountDueFormatted = _displayFormatter.Currency(amountDue, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||
CurrencyData = _currencies.GetCurrencyData(blob.Currency, true),
|
||||
|
@ -151,13 +151,10 @@ namespace BTCPayServer.Plugins.Crowdfund
|
||||
}
|
||||
|
||||
var invoices = await AppService.GetInvoicesForApp(_invoiceRepository, appData, lastResetDate);
|
||||
var completeInvoices = invoices.Where(IsComplete).ToArray();
|
||||
var pendingInvoices = invoices.Where(IsPending).ToArray();
|
||||
var paidInvoices = invoices.Where(IsPaid).ToArray();
|
||||
|
||||
var pendingPayments = _invoiceRepository.GetContributionsByPaymentMethodId(settings.TargetCurrency, pendingInvoices, !settings.EnforceTargetAmount);
|
||||
var currentPayments = _invoiceRepository.GetContributionsByPaymentMethodId(settings.TargetCurrency, completeInvoices, !settings.EnforceTargetAmount);
|
||||
|
||||
var currentPayments = _invoiceRepository.GetContributionsByPaymentMethodId(settings.TargetCurrency, invoices, !settings.EnforceTargetAmount);
|
||||
|
||||
var paidInvoices = invoices.Where(e => e.Status is InvoiceStatus.Settled or InvoiceStatus.Processing).ToArray();
|
||||
var perkCount = paidInvoices
|
||||
.Where(entity => !string.IsNullOrEmpty(entity.Metadata.ItemCode))
|
||||
.GroupBy(entity => entity.Metadata.ItemCode)
|
||||
@ -225,14 +222,14 @@ namespace BTCPayServer.Plugins.Crowdfund
|
||||
Info = new ViewCrowdfundViewModel.CrowdfundInfo
|
||||
{
|
||||
TotalContributors = paidInvoices.Length,
|
||||
ProgressPercentage = (currentPayments.TotalCurrency / settings.TargetAmount) * 100,
|
||||
PendingProgressPercentage = (pendingPayments.TotalCurrency / settings.TargetAmount) * 100,
|
||||
ProgressPercentage = (currentPayments.TotalSettled / settings.TargetAmount) * 100,
|
||||
PendingProgressPercentage = (currentPayments.TotalProcessing / settings.TargetAmount) * 100,
|
||||
LastUpdated = DateTime.UtcNow,
|
||||
PaymentStats = GetPaymentStats(currentPayments, pendingPayments),
|
||||
PaymentStats = GetPaymentStats(currentPayments),
|
||||
LastResetDate = lastResetDate,
|
||||
NextResetDate = nextResetDate,
|
||||
CurrentPendingAmount = pendingPayments.TotalCurrency,
|
||||
CurrentAmount = currentPayments.TotalCurrency
|
||||
CurrentPendingAmount = currentPayments.TotalProcessing,
|
||||
CurrentAmount = currentPayments.TotalSettled
|
||||
}
|
||||
};
|
||||
var httpContext = _httpContextAccessor.HttpContext;
|
||||
@ -243,29 +240,18 @@ namespace BTCPayServer.Plugins.Crowdfund
|
||||
return vm;
|
||||
}
|
||||
|
||||
private Dictionary<string, PaymentStat> GetPaymentStats(InvoiceStatistics stats, InvoiceStatistics pendingSats)
|
||||
{
|
||||
var r = new Dictionary<string, PaymentStat>();
|
||||
var allStats = stats.Concat(pendingSats);
|
||||
var total = allStats
|
||||
.Select(s => s.Value.CurrencyValue).Sum();
|
||||
foreach (var kv in allStats
|
||||
.GroupBy(k => k.Key, k => k.Value)
|
||||
.Select(g => (g.Key,
|
||||
PaymentCurrency: g.Select(s => s.Currency).First(),
|
||||
CurrencyValue: g.Sum(s => s.CurrencyValue))))
|
||||
{
|
||||
var pmi = PaymentMethodId.Parse(kv.Key);
|
||||
r.TryAdd(kv.Key, new PaymentStat()
|
||||
private Dictionary<string, PaymentStat> GetPaymentStats(InvoiceStatistics stats)
|
||||
=> stats.Total is 0.0m ? new Dictionary<string, PaymentStat>() : stats.ToDictionary(kv => kv.Key, kv =>
|
||||
{
|
||||
Label = _prettyNameProvider.PrettyName(pmi),
|
||||
Percent = (kv.CurrencyValue / total) * 100.0m,
|
||||
// Note that the LNURL will have the same LN
|
||||
IsLightning = pmi == PaymentTypes.LN.GetPaymentMethodId(kv.PaymentCurrency)
|
||||
var pmi = PaymentMethodId.Parse(kv.Key);
|
||||
return new PaymentStat()
|
||||
{
|
||||
Label = _prettyNameProvider.PrettyName(pmi),
|
||||
Percent = (kv.Value.CurrencyValue / stats.Total) * 100.0m,
|
||||
// Note that the LNURL will have the same LN
|
||||
IsLightning = pmi == PaymentTypes.LN.GetPaymentMethodId(kv.Value.Currency)
|
||||
};
|
||||
});
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public override Task SetDefaultSettings(AppData appData, string defaultCurrency)
|
||||
{
|
||||
@ -279,20 +265,5 @@ namespace BTCPayServer.Plugins.Crowdfund
|
||||
return Task.FromResult(_linkGenerator.GetPathByAction(nameof(UICrowdfundController.ViewCrowdfund),
|
||||
"UICrowdfund", new { appId = app.Id }, _options.Value.RootPath)!);
|
||||
}
|
||||
|
||||
private static bool IsPaid(InvoiceEntity entity)
|
||||
{
|
||||
return entity.Status == InvoiceStatus.Settled || entity.Status == InvoiceStatus.Processing;
|
||||
}
|
||||
|
||||
private static bool IsPending(InvoiceEntity entity)
|
||||
{
|
||||
return entity.Status != InvoiceStatus.Settled;
|
||||
}
|
||||
|
||||
private static bool IsComplete(InvoiceEntity entity)
|
||||
{
|
||||
return entity.Status == InvoiceStatus.Settled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -830,14 +830,6 @@ namespace BTCPayServer.Services.Invoices
|
||||
Status == InvoiceStatus.Invalid;
|
||||
}
|
||||
|
||||
public bool IsSettled()
|
||||
{
|
||||
return
|
||||
Status == InvoiceStatus.Settled ||
|
||||
(Status == InvoiceStatus.Expired &&
|
||||
ExceptionStatus is InvoiceExceptionStatus.PaidLate or InvoiceExceptionStatus.PaidOver);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Status + ExceptionStatus switch
|
||||
|
@ -70,13 +70,13 @@ namespace BTCPayServer.Services.Invoices
|
||||
public async Task<InvoiceEntity> GetInvoiceFromAddress(PaymentMethodId paymentMethodId, string address)
|
||||
{
|
||||
using var db = _applicationDbContextFactory.CreateContext();
|
||||
var row = (await db.AddressInvoices
|
||||
.Include(a => a.InvoiceData.Payments)
|
||||
.Where(a => a.Address == address && a.PaymentMethodId == paymentMethodId.ToString())
|
||||
.Select(a => a.InvoiceData)
|
||||
.FirstOrDefaultAsync());
|
||||
return row is null ? null : ToEntity(row);
|
||||
}
|
||||
var row = (await db.AddressInvoices
|
||||
.Include(a => a.InvoiceData.Payments)
|
||||
.Where(a => a.Address == address && a.PaymentMethodId == paymentMethodId.ToString())
|
||||
.Select(a => a.InvoiceData)
|
||||
.FirstOrDefaultAsync());
|
||||
return row is null ? null : ToEntity(row);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all invoices which either:
|
||||
@ -820,6 +820,8 @@ retry:
|
||||
|
||||
public InvoiceStatistics GetContributionsByPaymentMethodId(string currency, InvoiceEntity[] invoices, bool softcap)
|
||||
{
|
||||
decimal totalSettledCurrency = 0.0m;
|
||||
decimal totalProcessingCurrency = 0.0m;
|
||||
var contributions = invoices
|
||||
.Where(p => p.Currency.Equals(currency, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(p =>
|
||||
@ -828,30 +830,42 @@ retry:
|
||||
{
|
||||
GroupKey = p.Currency,
|
||||
Currency = p.Currency,
|
||||
CurrencyValue = p.Price,
|
||||
Settled = p.GetInvoiceState().IsSettled()
|
||||
CurrencyValue = p.Price
|
||||
};
|
||||
contribution.Value = contribution.CurrencyValue;
|
||||
|
||||
// For hardcap, we count newly created invoices as part of the contributions
|
||||
if (!softcap && p.Status == InvoiceStatus.New)
|
||||
{
|
||||
totalProcessingCurrency += contribution.CurrencyValue;
|
||||
return new[] { contribution };
|
||||
}
|
||||
|
||||
var markedSettled = p is
|
||||
{
|
||||
Status: InvoiceStatus.Settled,
|
||||
ExceptionStatus: InvoiceExceptionStatus.Marked
|
||||
};
|
||||
// If the user get a donation via other mean, he can register an invoice manually for such amount
|
||||
// then mark the invoice as complete
|
||||
var payments = p.GetPayments(true);
|
||||
if (payments.Count == 0 &&
|
||||
p.ExceptionStatus == InvoiceExceptionStatus.Marked &&
|
||||
p.Status == InvoiceStatus.Settled)
|
||||
if (payments.Count == 0 && markedSettled)
|
||||
{
|
||||
totalSettledCurrency += contribution.CurrencyValue;
|
||||
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)
|
||||
if (p is
|
||||
{
|
||||
Status: InvoiceStatus.Invalid,
|
||||
ExceptionStatus: InvoiceExceptionStatus.Marked
|
||||
})
|
||||
{
|
||||
contribution.CurrencyValue = 0m;
|
||||
contribution.Value = 0m;
|
||||
return new[] { contribution };
|
||||
}
|
||||
|
||||
// Else, we just sum the payments
|
||||
return payments
|
||||
@ -863,9 +877,22 @@ retry:
|
||||
Currency = pay.Currency,
|
||||
CurrencyValue = pay.InvoicePaidAmount.Net,
|
||||
Value = pay.PaidAmount.Net,
|
||||
Divisibility = pay.Divisibility,
|
||||
Settled = p.GetInvoiceState().IsSettled()
|
||||
Divisibility = pay.Divisibility
|
||||
};
|
||||
|
||||
// If paid before expiration or marked settled, account the payments...
|
||||
if (pay.ReceivedTime <= p.ExpirationTime || markedSettled)
|
||||
{
|
||||
if (pay.Status is PaymentStatus.Settled)
|
||||
totalSettledCurrency += pay.InvoicePaidAmount.Net;
|
||||
else if (pay.Status is PaymentStatus.Processing)
|
||||
totalProcessingCurrency += pay.InvoicePaidAmount.Net;
|
||||
}
|
||||
else
|
||||
{
|
||||
paymentMethodContribution.CurrencyValue = 0m;
|
||||
paymentMethodContribution.Value = 0m;
|
||||
}
|
||||
return paymentMethodContribution;
|
||||
})
|
||||
.ToArray();
|
||||
@ -874,12 +901,16 @@ retry:
|
||||
.ToDictionary(p => p.Key, p => new InvoiceStatistics.Contribution
|
||||
{
|
||||
Currency = p.Select(p => p.Currency).First(),
|
||||
Settled = p.All(v => v.Settled),
|
||||
Divisibility = p.Max(p => p.Divisibility),
|
||||
Value = p.Select(v => v.Value).Sum(),
|
||||
CurrencyValue = p.Select(v => v.CurrencyValue).Sum()
|
||||
});
|
||||
return new InvoiceStatistics(contributions);
|
||||
return new InvoiceStatistics(contributions)
|
||||
{
|
||||
TotalSettled = totalSettledCurrency,
|
||||
TotalProcessing = totalProcessingCurrency,
|
||||
Total = totalSettledCurrency + totalProcessingCurrency
|
||||
};
|
||||
}
|
||||
|
||||
private string GetGroupKey(PaymentEntity pay)
|
||||
@ -967,15 +998,18 @@ retry:
|
||||
{
|
||||
public InvoiceStatistics(IEnumerable<KeyValuePair<string, Contribution>> collection) : base(collection)
|
||||
{
|
||||
TotalCurrency = Values.Select(v => v.CurrencyValue).Sum();
|
||||
}
|
||||
public decimal TotalCurrency { get; }
|
||||
public decimal TotalSettled { get; set; }
|
||||
public decimal TotalProcessing { get; set; }
|
||||
/// <summary>
|
||||
/// <see cref="TotalSettled"/> + <see cref="TotalProcessing"/>
|
||||
/// </summary>
|
||||
public decimal Total { get; set; }
|
||||
|
||||
public class Contribution
|
||||
{
|
||||
public string Currency { get; set; }
|
||||
public int Divisibility { get; set; }
|
||||
public bool Settled { get; set; }
|
||||
public decimal Value { get; set; }
|
||||
public decimal CurrencyValue { get; set; }
|
||||
public string GroupKey { get; set; }
|
||||
|
Loading…
Reference in New Issue
Block a user