Make sure to only select accounted payments where we should (#2523)

This commit is contained in:
Nicolas Dorier 2021-05-14 16:16:19 +09:00 committed by GitHub
parent 776ded0b7e
commit c551e5cd0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 48 additions and 42 deletions

View file

@ -320,7 +320,7 @@ namespace BTCPayServer.Tests
await TestUtils.EventuallyAsync(async () =>
{
var invoice = await invoiceRepository.GetInvoice(invoiceId);
var payments = invoice.GetPayments();
var payments = invoice.GetPayments(false);
Assert.Equal(2, payments.Count);
var originalPayment = payments[0];
var coinjoinPayment = payments[1];
@ -1088,7 +1088,7 @@ retry:
{
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
Assert.Equal(InvoiceStatusLegacy.Paid, invoiceEntity.Status);
Assert.Contains(invoiceEntity.GetPayments(), p => p.Accounted &&
Assert.Contains(invoiceEntity.GetPayments(false), p => p.Accounted &&
((BitcoinLikePaymentData)p.GetCryptoPaymentData()).PayjoinInformation is null);
});
////Assert.Contains(receiverWalletPayJoinState.GetRecords(), item => item.InvoiceId == invoice7.Id && item.TxSeen);
@ -1117,8 +1117,8 @@ retry:
{
var invoiceEntity = await tester.PayTester.GetService<InvoiceRepository>().GetInvoice(invoice7.Id);
Assert.Equal(InvoiceStatusLegacy.New, invoiceEntity.Status);
Assert.True(invoiceEntity.GetPayments().All(p => !p.Accounted));
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData().First().PayjoinInformation.ContributedOutPoints[0];
Assert.True(invoiceEntity.GetPayments(false).All(p => !p.Accounted));
ourOutpoint = invoiceEntity.GetAllBitcoinPaymentData(false).First().PayjoinInformation.ContributedOutPoints[0];
});
var payjoinRepository = tester.PayTester.GetService<PayJoinRepository>();
// The outpoint should now be available for next pj selection

View file

@ -1639,8 +1639,8 @@ namespace BTCPayServer.Tests
{
var i = await tester.PayTester.InvoiceRepository.GetInvoice(invoice2.Id);
Assert.Equal(InvoiceStatusLegacy.New, i.Status);
Assert.Single(i.GetPayments());
Assert.False(i.GetPayments().First().Accounted);
Assert.Single(i.GetPayments(false));
Assert.False(i.GetPayments(false).First().Accounted);
});
Logs.Tester.LogInformation(
@ -1672,8 +1672,8 @@ namespace BTCPayServer.Tests
await TestUtils.EventuallyAsync(async () =>
{
var invoiceEntity = await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id);
var btcPayments = invoiceEntity.GetAllBitcoinPaymentData().ToArray();
var payments = invoiceEntity.GetPayments().ToArray();
var btcPayments = invoiceEntity.GetAllBitcoinPaymentData(false).ToArray();
var payments = invoiceEntity.GetPayments(false).ToArray();
Assert.Equal(tx1, btcPayments[0].Outpoint.Hash);
Assert.False(payments[0].Accounted);
Assert.Equal(tx1Bump, payments[1].Outpoint.Hash);

View file

@ -283,7 +283,7 @@ namespace BTCPayServer.Controllers.GreenField
[Authorize(Policy = Policies.CanViewInvoices,
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods")]
public async Task<IActionResult> GetInvoicePaymentMethods(string storeId, string invoiceId)
public async Task<IActionResult> GetInvoicePaymentMethods(string storeId, string invoiceId, bool onlyAccountedPayments = true)
{
var store = HttpContext.GetStoreData();
if (store == null)
@ -297,7 +297,7 @@ namespace BTCPayServer.Controllers.GreenField
return InvoiceNotFound();
}
return Ok(ToPaymentMethodModels(invoice));
return Ok(ToPaymentMethodModels(invoice, onlyAccountedPayments));
}
[Authorize(Policy = Policies.CanViewInvoices,
@ -336,14 +336,14 @@ namespace BTCPayServer.Controllers.GreenField
return this.CreateAPIError(404, "store-not-found", "The store was not found");
}
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity)
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity, bool includeAccountedPaymentOnly)
{
return entity.GetPaymentMethods().Select(
method =>
{
var accounting = method.Calculate();
var details = method.GetPaymentMethodDetails();
var payments = method.ParentEntity.GetPayments().Where(paymentEntity =>
var payments = method.ParentEntity.GetPayments(includeAccountedPaymentOnly).Where(paymentEntity =>
paymentEntity.GetPaymentMethodId() == method.GetId());
return new InvoicePaymentMethodDataModel()

View file

@ -359,7 +359,7 @@ namespace BTCPayServer.Controllers
return new InvoiceDetailsModel
{
Archived = invoice.Archived,
Payments = invoice.GetPayments(),
Payments = invoice.GetPayments(false),
CryptoPayments = invoice.GetPaymentMethods().Select(
data =>
{
@ -561,7 +561,7 @@ namespace BTCPayServer.Controllers
Status = invoice.StatusString,
#pragma warning restore CS0618 // Type or member is obsolete
NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
IsMultiCurrency = invoice.GetPayments(false).Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1,
StoreId = store.Id,
AvailableCryptos = invoice.GetPaymentMethods()
.Where(i => i.Network != null)

View file

@ -133,9 +133,9 @@ namespace BTCPayServer
finally { try { webSocket.Dispose(); } catch { } }
}
public static IEnumerable<BitcoinLikePaymentData> GetAllBitcoinPaymentData(this InvoiceEntity invoice)
public static IEnumerable<BitcoinLikePaymentData> GetAllBitcoinPaymentData(this InvoiceEntity invoice, bool accountedOnly)
{
return invoice.GetPayments()
return invoice.GetPayments(accountedOnly)
.Where(p => p.GetPaymentMethodId()?.PaymentType == PaymentTypes.BTCLike)
.Select(p => (BitcoinLikePaymentData)p.GetCryptoPaymentData())
.Where(data => data != null);

View file

@ -101,7 +101,7 @@ namespace BTCPayServer.HostedServices
}
}
if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments().Count != 0 && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidPartial)
if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments(true).Count != 0 && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidPartial)
{
invoice.ExceptionStatus = InvoiceExceptionStatus.PaidPartial;
context.MarkDirty();
@ -335,7 +335,7 @@ namespace BTCPayServer.HostedServices
{
bool extendInvoiceMonitoring = false;
var updateConfirmationCountIfNeeded = invoice
.GetPayments()
.GetPayments(false)
.Select<PaymentEntity, Task<PaymentEntity>>(async payment =>
{
var paymentData = payment.GetCryptoPaymentData();

View file

@ -46,7 +46,7 @@ namespace BTCPayServer.HostedServices
UpdateTransactionLabel.InvoiceLabelTemplate(invoiceEvent.Invoice.Id)
};
if (invoiceEvent.Invoice.GetPayments(invoiceEvent.Payment.GetCryptoCode()).Any(entity =>
if (invoiceEvent.Invoice.GetPayments(invoiceEvent.Payment.GetCryptoCode(), false).Any(entity =>
entity.GetCryptoPaymentData() is BitcoinLikePaymentData pData &&
pData.PayjoinInformation?.CoinjoinTransactionHash == transactionId))
{

View file

@ -111,8 +111,7 @@ namespace BTCPayServer.PaymentRequest
State = state,
StateFormatted = state.ToString(),
Payments = entity
.GetPayments()
.Where(p => p.Accounted)
.GetPayments(true)
.Select(paymentEntity =>
{
var paymentData = paymentEntity.GetCryptoPaymentData();

View file

@ -157,7 +157,7 @@ namespace BTCPayServer.Payments.Bitcoin
output.matchedOutput.Value, output.outPoint,
evt.TransactionData.Transaction.RBF, output.Item1.KeyPath);
var alreadyExist = invoice.GetAllBitcoinPaymentData().Where(c => c.GetPaymentId() == paymentData.GetPaymentId()).Any();
var alreadyExist = invoice.GetAllBitcoinPaymentData(false).Where(c => c.GetPaymentId() == paymentData.GetPaymentId()).Any();
if (!alreadyExist)
{
var payment = await _InvoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, paymentData, network);
@ -220,7 +220,7 @@ namespace BTCPayServer.Payments.Bitcoin
{
List<PaymentEntity> updatedPaymentEntities = new List<PaymentEntity>();
var transactions = await wallet.GetTransactions(invoice.GetAllBitcoinPaymentData()
var transactions = await wallet.GetTransactions(invoice.GetAllBitcoinPaymentData(false)
.Select(p => p.Outpoint.Hash)
.ToArray(), true);
bool? originalPJBroadcasted = null;
@ -228,7 +228,7 @@ namespace BTCPayServer.Payments.Bitcoin
bool cjPJBroadcasted = false;
PayjoinInformation payjoinInformation = null;
var paymentEntitiesByPrevOut = new Dictionary<OutPoint, PaymentEntity>();
foreach (var payment in invoice.GetPayments(wallet.Network))
foreach (var payment in invoice.GetPayments(wallet.Network, false))
{
if (payment.GetPaymentMethodId()?.PaymentType != PaymentTypes.BTCLike)
continue;
@ -347,7 +347,7 @@ namespace BTCPayServer.Payments.Bitcoin
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, true);
if (invoice == null)
continue;
var alreadyAccounted = invoice.GetAllBitcoinPaymentData().Select(p => p.Outpoint).ToHashSet();
var alreadyAccounted = invoice.GetAllBitcoinPaymentData(false).Select(p => p.Outpoint).ToHashSet();
var strategy = GetDerivationStrategy(invoice, network);
if (strategy == null)
continue;

View file

@ -300,7 +300,7 @@ namespace BTCPayServer.Payments.PayJoin
paymentAddress = paymentDetails.GetDepositAddress(network.NBitcoinNetwork);
paymentAddressIndex = paymentDetails.KeyPath;
if (invoice.GetAllBitcoinPaymentData().Any())
if (invoice.GetAllBitcoinPaymentData(false).Any())
{
ctx.DoNotBroadcast();
return UnprocessableEntity(CreatePayjoinError("already-paid",

View file

@ -242,7 +242,7 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
.Select(entity => (
Invoice: entity,
PaymentMethodDetails: entity.GetPaymentMethods().TryGet(paymentMethodId),
ExistingPayments: entity.GetPayments(network).Select(paymentEntity => (Payment: paymentEntity,
ExistingPayments: entity.GetPayments(network, true).Select(paymentEntity => (Payment: paymentEntity,
PaymentData: (EthereumLikePaymentData)paymentEntity.GetCryptoPaymentData(),
Invoice: entity))
)).Where(tuple => tuple.PaymentMethodDetails?.GetPaymentMethodDetails()?.Activated is true).ToList();

View file

@ -372,7 +372,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
private IEnumerable<PaymentEntity> GetAllMoneroLikePayments(InvoiceEntity invoice, string cryptoCode)
{
return invoice.GetPayments()
return invoice.GetPayments(false)
.Where(p => p.GetPaymentMethodId() == new PaymentMethodId(cryptoCode, MoneroPaymentType.Instance));
}
}

View file

@ -359,7 +359,7 @@ namespace BTCPayServer.Services.Apps
// 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();
var payments = p.GetPayments(true);
if (payments.Count == 0 &&
p.ExceptionStatus == InvoiceExceptionStatus.Marked &&
p.Status == InvoiceStatusLegacy.Complete)

View file

@ -59,11 +59,8 @@ namespace BTCPayServer.Services.Invoices.Export
var currency = Currencies.GetNumberFormatInfo(invoice.Currency, true);
var invoiceDue = invoice.Price;
// in this first version we are only exporting invoices that were paid
foreach (var payment in invoice.GetPayments())
foreach (var payment in invoice.GetPayments(true))
{
// not accounted payments are payments which got double spent like RBfed
if (!payment.Accounted)
continue;
var cryptoCode = payment.GetPaymentMethodId().CryptoCode;
var pdata = payment.GetCryptoPaymentData();

View file

@ -326,17 +326,17 @@ namespace BTCPayServer.Services.Invoices
public List<PaymentEntity> Payments { get; set; }
#pragma warning disable CS0618
public List<PaymentEntity> GetPayments()
public List<PaymentEntity> GetPayments(bool accountedOnly)
{
return Payments?.Where(entity => entity.GetPaymentMethodId() != null).ToList() ?? new List<PaymentEntity>();
return Payments?.Where(entity => entity.GetPaymentMethodId() != null && (!accountedOnly || entity.Accounted)).ToList() ?? new List<PaymentEntity>();
}
public List<PaymentEntity> GetPayments(string cryptoCode)
public List<PaymentEntity> GetPayments(string cryptoCode, bool accountedOnly)
{
return GetPayments().Where(p => p.CryptoCode == cryptoCode).ToList();
return GetPayments(accountedOnly).Where(p => p.CryptoCode == cryptoCode).ToList();
}
public List<PaymentEntity> GetPayments(BTCPayNetworkBase network)
public List<PaymentEntity> GetPayments(BTCPayNetworkBase network, bool accountedOnly)
{
return GetPayments(network.CryptoCode);
return GetPayments(network.CryptoCode, accountedOnly);
}
#pragma warning restore CS0618
public bool Refundable { get; set; }
@ -449,7 +449,7 @@ namespace BTCPayServer.Services.Invoices
var paymentId = info.GetId();
cryptoInfo.Url = ServerUrl.WithTrailingSlash() + $"i/{paymentId}/{Id}";
cryptoInfo.Payments = GetPayments(info.Network).Select(entity =>
cryptoInfo.Payments = GetPayments(info.Network, true).Select(entity =>
{
var data = entity.GetCryptoPaymentData();
return new InvoicePaymentInfo()
@ -980,8 +980,8 @@ namespace BTCPayServer.Services.Invoices
bool paidEnough = paid >= Extensions.RoundUp(totalDue, precision);
int txRequired = 0;
_ = ParentEntity.GetPayments()
.Where(p => p.Accounted && paymentPredicate(p))
_ = ParentEntity.GetPayments(true)
.Where(p => paymentPredicate(p))
.OrderBy(p => p.ReceivedTime)
.Select(_ =>
{

View file

@ -347,6 +347,16 @@
"schema": {
"type": "string"
}
},
{
"name": "onlyAccountedPayments",
"in": "query",
"required": false,
"description": "If default or true, only returns payments which are accounted (in Bitcoin, this mean not returning RBF'd or double spent payments)",
"schema": {
"type": "boolean",
"default": true
}
}
],
"description": "View information about the specified invoice's payment methods",