diff --git a/BTCPayServer/HostedServices/InvoiceWatcher.cs b/BTCPayServer/HostedServices/InvoiceWatcher.cs index aa88ba73b..7c143ff74 100644 --- a/BTCPayServer/HostedServices/InvoiceWatcher.cs +++ b/BTCPayServer/HostedServices/InvoiceWatcher.cs @@ -288,31 +288,18 @@ namespace BTCPayServer.HostedServices if (invoice.Status == InvoiceStatus.Complete || ((invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired) && invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) { - var updateConfirmationCountIfNeeded = invoice - .GetPayments() - .Select(async payment => - { - var paymentNetwork = _NetworkProvider.GetNetwork(payment.GetCryptoCode()); - var paymentData = payment.GetCryptoPaymentData(); - if (paymentData is Payments.Bitcoin.BitcoinLikePaymentData onChainPaymentData) - { - // Do update if confirmation count in the paymentData is not up to date - if ((onChainPaymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted) - && (onChainPaymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) - { - var transactionResult = await _ExplorerClientProvider.GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(onChainPaymentData.Outpoint.Hash); - var confirmationCount = transactionResult?.Confirmations ?? 0; - onChainPaymentData.ConfirmationCount = confirmationCount; - payment.SetCryptoPaymentData(onChainPaymentData); - await _InvoiceRepository.UpdatePayments(new List { payment }); - } - } - }) - .ToArray(); - await Task.WhenAll(updateConfirmationCountIfNeeded); + var extendInvoiceMonitoring = await UpdateConfirmationCount(invoice); - if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id)) + // we extend monitor time if we haven't reached max confirmation count + // say user used low fee and we only got 3 confirmations right before it's time to remove + if (extendInvoiceMonitoring) + { + await _InvoiceRepository.ExtendInvoiceMonitor(invoice.Id); + } + else if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id)) + { _EventAggregator.Publish(new InvoiceStopWatchedEvent(invoice.Id)); + } break; } @@ -330,6 +317,46 @@ namespace BTCPayServer.HostedServices } } + private async Task UpdateConfirmationCount(InvoiceEntity invoice) + { + bool extendInvoiceMonitoring = false; + var updateConfirmationCountIfNeeded = invoice + .GetPayments() + .Select>(async payment => + { + var paymentNetwork = _NetworkProvider.GetNetwork(payment.GetCryptoCode()); + var paymentData = payment.GetCryptoPaymentData(); + if (paymentData is Payments.Bitcoin.BitcoinLikePaymentData onChainPaymentData) + { + // Do update if confirmation count in the paymentData is not up to date + if ((onChainPaymentData.ConfirmationCount < paymentNetwork.MaxTrackedConfirmation && payment.Accounted) + && (onChainPaymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) + { + var transactionResult = await _ExplorerClientProvider.GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(onChainPaymentData.Outpoint.Hash); + var confirmationCount = transactionResult?.Confirmations ?? 0; + onChainPaymentData.ConfirmationCount = confirmationCount; + payment.SetCryptoPaymentData(onChainPaymentData); + + // we want to extend invoice monitoring until we reach max confirmations on all onchain payment methods + if (confirmationCount < paymentNetwork.MaxTrackedConfirmation) + extendInvoiceMonitoring = true; + + return payment; + } + } + return null; + }) + .ToArray(); + await Task.WhenAll(updateConfirmationCountIfNeeded); + var updatedPaymentData = updateConfirmationCountIfNeeded.Where(a => a.Result != null).Select(a => a.Result).ToList(); + if (updatedPaymentData.Count > 0) + { + await _InvoiceRepository.UpdatePayments(updatedPaymentData); + } + + return extendInvoiceMonitoring; + } + public async Task StopAsync(CancellationToken cancellationToken) { if (_Cts == null) diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index 5d4467e1f..529cd7184 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -120,6 +120,20 @@ retry: } } + public async Task ExtendInvoiceMonitor(string invoiceId) + { + using (var ctx = _ContextFactory.CreateContext()) + { + var invoiceData = await ctx.Invoices.FindAsync(invoiceId); + + var invoice = ToObject(invoiceData.Blob); + invoice.MonitoringExpiration = invoice.MonitoringExpiration.AddHours(1); + invoiceData.Blob = ToBytes(invoice, null); + + await ctx.SaveChangesAsync(); + } + } + public async Task CreateInvoiceAsync(string storeId, InvoiceEntity invoice) { List textSearch = new List();