Refactor confirmation count tracking (#6215)

This commit is contained in:
Nicolas Dorier
2024-09-17 17:28:58 +09:00
committed by GitHub
parent 397452a7fe
commit 0f93581ff5
22 changed files with 164 additions and 251 deletions

View File

@@ -39,7 +39,6 @@ namespace BTCPayServer.Data
public DbSet<PaymentRequestData> PaymentRequests { get; set; }
public DbSet<PaymentData> Payments { get; set; }
public DbSet<PayoutData> Payouts { get; set; }
public DbSet<PendingInvoiceData> PendingInvoices { get; set; }
public DbSet<PlannedTransaction> PlannedTransactions { get; set; }
public DbSet<PullPaymentData> PullPayments { get; set; }
public DbSet<RefundData> Refunds { get; set; }

View File

@@ -20,5 +20,6 @@
<ItemGroup>
<None Remove="DBScripts\001.InvoiceFunctions.sql" />
<None Remove="DBScripts\002.RefactorPayouts.sql" />
<None Remove="DBScripts\003.RefactorPendingInvoicesPayments.sql" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
CREATE OR REPLACE FUNCTION is_pending(status TEXT)
RETURNS BOOLEAN AS $$
SELECT status = 'Processing' OR status = 'New';
$$ LANGUAGE sql IMMUTABLE;
CREATE INDEX "IX_Invoices_Pending" ON "Invoices"((1)) WHERE is_pending("Status");
CREATE INDEX "IX_Payments_Pending" ON "Payments"((1)) WHERE is_pending("Status");
DROP TABLE "PendingInvoices";

View File

@@ -32,8 +32,9 @@ namespace BTCPayServer.Data
public static string GetOrderId(string blob) => throw new NotSupportedException();
public static string GetItemCode(string blob) => throw new NotSupportedException();
public static bool IsPending(string status) => throw new NotSupportedException();
[Timestamp]
[Timestamp]
// With this, update of InvoiceData will fail if the row was modified by another process
public uint XMin { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
@@ -51,7 +52,7 @@ namespace BTCPayServer.Data
.HasColumnType("NUMERIC");
builder.HasDbFunction(typeof(InvoiceData).GetMethod(nameof(GetOrderId), new[] { typeof(string) }), b => b.HasName("get_orderid"));
builder.HasDbFunction(typeof(InvoiceData).GetMethod(nameof(GetItemCode), new[] { typeof(string) }), b => b.HasName("get_itemcode"));
}
builder.HasDbFunction(typeof(InvoiceData).GetMethod(nameof(IsPending), new[] { typeof(string) }), b => b.HasName("is_pending"));
}
}
}

View File

@@ -31,7 +31,7 @@ namespace BTCPayServer.Data
[Obsolete("Use Status instead")]
public bool? Accounted { get; set; }
public PaymentStatus? Status { get; set; }
public static bool IsPending(PaymentStatus? status) => throw new NotSupportedException();
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<PaymentData>()
@@ -48,6 +48,7 @@ namespace BTCPayServer.Data
builder.Entity<PaymentData>()
.Property(o => o.Amount)
.HasColumnType("NUMERIC");
builder.HasDbFunction(typeof(PaymentData).GetMethod(nameof(IsPending), new[] { typeof(PaymentStatus?) }), b => b.HasName("is_pending"));
}
}
}

View File

@@ -0,0 +1,15 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240913034505_refactorpendinginvoicespayments")]
[DBScript("003.RefactorPendingInvoicesPayments.sql")]
public partial class refactorpendinginvoicespayments : DBScriptsMigration
{
}
}

View File

@@ -926,15 +926,6 @@ namespace BTCPayServer.Controllers
NetworkFeeMode.Never => 0,
_ => throw new NotImplementedException()
},
RequiredConfirmations = invoice.SpeedPolicy switch
{
SpeedPolicy.HighSpeed => 0,
SpeedPolicy.MediumSpeed => 1,
SpeedPolicy.LowMediumSpeed => 2,
SpeedPolicy.LowSpeed => 6,
_ => null
},
ReceivedConfirmations = handler is BitcoinLikePaymentHandler bh ? invoice.GetAllBitcoinPaymentData(bh, false).FirstOrDefault()?.ConfirmationCount : null,
Status = invoice.Status.ToString(),
// The Tweak is part of the PaymentMethodFee, but let's not show it in the UI as it's negligible.
NetworkFee = prompt.PaymentMethodFee - prompt.TweakFee,

View File

@@ -1,15 +0,0 @@
namespace BTCPayServer.Events
{
public class InvoiceStopWatchedEvent : IHasInvoiceId
{
public InvoiceStopWatchedEvent(string invoiceId)
{
this.InvoiceId = invoiceId;
}
public string InvoiceId { get; set; }
public override string ToString()
{
return $"Invoice {InvoiceId} is not monitored anymore.";
}
}
}

View File

@@ -323,8 +323,8 @@ namespace BTCPayServer
public static IEnumerable<BitcoinLikePaymentData> GetAllBitcoinPaymentData(this InvoiceEntity invoice, BitcoinLikePaymentHandler handler, bool accountedOnly)
{
return invoice.GetPayments(accountedOnly)
.Where(p => p.PaymentMethodId == handler.PaymentMethodId)
.Select(p => handler.ParsePaymentDetails(p.Details));
.Select(p => p.GetDetails<BitcoinLikePaymentData>(handler))
.Where(p => p is not null);
}
public static async Task<Dictionary<uint256, TransactionResult>> GetTransactions(this BTCPayWallet client, uint256[] hashes, bool includeOffchain = false, CancellationToken cts = default(CancellationToken))

View File

@@ -24,7 +24,6 @@ namespace BTCPayServer.HostedServices
{
Subscribe<InvoiceEvent>();
Subscribe<InvoiceDataChangedEvent>();
Subscribe<InvoiceStopWatchedEvent>();
Subscribe<InvoiceIPNEvent>();
}

View File

@@ -191,9 +191,9 @@ namespace BTCPayServer.HostedServices
}
}
private async Task Wait(string invoiceId)
private async Task Wait(string invoiceId) => await Wait(await _invoiceRepository.GetInvoice(invoiceId));
private async Task Wait(InvoiceEntity invoice)
{
var invoice = await _invoiceRepository.GetInvoice(invoiceId);
try
{
// add 1 second to ensure watch won't trigger moments before invoice expires
@@ -202,7 +202,7 @@ namespace BTCPayServer.HostedServices
{
await Task.Delay(delay, _Cts.Token);
}
Watch(invoiceId);
Watch(invoice.Id);
// add 1 second to ensure watch won't trigger moments before monitoring expires
delay = invoice.MonitoringExpiration.AddSeconds(1) - DateTimeOffset.UtcNow;
@@ -210,7 +210,7 @@ namespace BTCPayServer.HostedServices
{
await Task.Delay(delay, _Cts.Token);
}
Watch(invoiceId);
Watch(invoice.Id);
}
catch when (_Cts.IsCancellationRequested)
{ }
@@ -255,8 +255,8 @@ namespace BTCPayServer.HostedServices
private async Task WaitPendingInvoices()
{
await Task.WhenAll((await _invoiceRepository.GetPendingInvoiceIds())
.Select(id => Wait(id)).ToArray());
await Task.WhenAll((await _invoiceRepository.GetPendingInvoices())
.Select(i => Wait(i)).ToArray());
}
async Task StartLoop(CancellationToken cancellation)
@@ -292,24 +292,6 @@ namespace BTCPayServer.HostedServices
_eventAggregator.Publish(evt, evt.GetType());
}
if (invoice.Status == InvoiceStatus.Settled ||
((invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired) && invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
{
var extendInvoiceMonitoring = await UpdateConfirmationCount(invoice);
// 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;
}
if (updateContext.Events.Count == 0)
break;
}
@@ -324,48 +306,6 @@ namespace BTCPayServer.HostedServices
}
}
// TODO: Move that in the NBXplorerListener
private async Task<bool> UpdateConfirmationCount(InvoiceEntity invoice)
{
bool extendInvoiceMonitoring = false;
var updateConfirmationCountIfNeeded = invoice
.GetPayments(true)
.Select<PaymentEntity, Task<PaymentEntity>>(async payment =>
{
if (!_handlers.TryGetValue(payment.PaymentMethodId, out var h) || h is not Payments.Bitcoin.BitcoinLikePaymentHandler handler)
return null;
var onChainPaymentData = handler.ParsePaymentDetails(payment.Details);
var network = handler.Network;
// Do update if confirmation count in the paymentData is not up to date
if (onChainPaymentData.ConfirmationCount < network.MaxTrackedConfirmation)
{
var client = _explorerClientProvider.GetExplorerClient(payment.Currency);
var transactionResult = client is null ? null : await client.GetTransactionAsync(onChainPaymentData.Outpoint.Hash);
var confirmationCount = transactionResult?.Confirmations ?? 0;
onChainPaymentData.ConfirmationCount = confirmationCount;
payment.Status = NBXplorerListener.IsSettled(invoice, onChainPaymentData) ? PaymentStatus.Settled : PaymentStatus.Processing;
payment.SetDetails(handler, onChainPaymentData);
// we want to extend invoice monitoring until we reach max confirmations on all onchain payment methods
if (confirmationCount < network.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 _paymentService.UpdatePayments(updatedPaymentData);
}
return extendInvoiceMonitoring;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
if (_Cts == null)

View File

@@ -85,6 +85,13 @@ namespace BTCPayServer.Payments.Bitcoin
if (context.Model.Activated)
{
var paymentData = context.InvoiceEntity.GetAllBitcoinPaymentData(handler, true)?.MinBy(o => o.ConfirmationCount);
if (paymentData is not null)
{
context.Model.RequiredConfirmations = NBXplorerListener.ConfirmationRequired(context.InvoiceEntity, paymentData);
context.Model.ReceivedConfirmations = paymentData.ConfirmationCount;
}
// We're leading the way in Bitcoin community with adding UPPERCASE Bech32 addresses in QR Code
//
// Correct casing: Addresses in payment URI need to be …

View File

@@ -237,7 +237,7 @@ namespace BTCPayServer.Payments.Bitcoin
async Task UpdatePaymentStates(BTCPayWallet wallet)
{
var invoices = await _InvoiceRepository.GetPendingInvoices(skipNoPaymentInvoices: true);
var invoices = await _InvoiceRepository.GetInvoicesWithPendingPayments(PaymentTypes.CHAIN.GetPaymentMethodId(wallet.Network.CryptoCode));
await Task.WhenAll(invoices.Select(i => UpdatePaymentStates(wallet, i)).ToArray());
}
async Task<InvoiceEntity> UpdatePaymentStates(BTCPayWallet wallet, string invoiceId, bool fireEvents = true)
@@ -348,10 +348,6 @@ namespace BTCPayServer.Payments.Bitcoin
paymentEntitiesByPrevOut.TryAdd(prevout, payment);
}
// if needed add invoice back to pending to track number of confirmations
if (paymentData.ConfirmationCount < wallet.Network.MaxTrackedConfirmation)
await _InvoiceRepository.AddPendingInvoiceIfNotPresent(invoice.Id);
if (updated)
updatedPaymentEntities.Add(payment);
}
@@ -372,32 +368,25 @@ namespace BTCPayServer.Payments.Bitcoin
return invoice;
}
public static bool IsSettled(InvoiceEntity invoice, BitcoinLikePaymentData paymentData)
public static int ConfirmationRequired(InvoiceEntity invoice, BitcoinLikePaymentData paymentData)
=> (invoice, paymentData) switch
{
if (invoice.SpeedPolicy == SpeedPolicy.HighSpeed)
{
return paymentData.ConfirmationCount >= 1 || !paymentData.RBF;
}
else if (invoice.SpeedPolicy == SpeedPolicy.MediumSpeed)
{
return paymentData.ConfirmationCount >= 1;
}
else if (invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed)
{
return paymentData.ConfirmationCount >= 2;
}
else if (invoice.SpeedPolicy == SpeedPolicy.LowSpeed)
{
return paymentData.ConfirmationCount >= 6;
}
return false;
}
({ SpeedPolicy: SpeedPolicy.HighSpeed }, { RBF: true }) => 1,
({ SpeedPolicy: SpeedPolicy.HighSpeed }, _) => 0,
({ SpeedPolicy: SpeedPolicy.MediumSpeed }, _) => 1,
({ SpeedPolicy: SpeedPolicy.LowMediumSpeed }, _) => 2,
({ SpeedPolicy: SpeedPolicy.LowSpeed }, _) => 6,
_ => 6,
};
static bool IsSettled(InvoiceEntity invoice, BitcoinLikePaymentData paymentData)
=> ConfirmationRequired(invoice, paymentData) <= paymentData.ConfirmationCount;
private async Task<int> FindPaymentViaPolling(BTCPayWallet wallet, BTCPayNetwork network)
{
var handler = _handlers.GetBitcoinHandler(wallet.Network);
int totalPayment = 0;
var invoices = await _InvoiceRepository.GetPendingInvoices(true);
var invoices = await _InvoiceRepository.GetInvoicesWithPendingPayments(PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode), true);
var coinsPerDerivationStrategy =
new Dictionary<DerivationStrategyBase, ReceivedCoin[]>();
foreach (var i in invoices)

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Services.Altcoins.Monero.Services;
using BTCPayServer.Services.Invoices;
namespace BTCPayServer.Services.Altcoins.Monero.Payments
@@ -8,15 +10,18 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
public class MoneroPaymentModelExtension : IPaymentModelExtension
{
private readonly BTCPayNetworkBase _network;
private readonly PaymentMethodHandlerDictionary _handlers;
private readonly IPaymentLinkExtension paymentLinkExtension;
public MoneroPaymentModelExtension(
PaymentMethodId paymentMethodId,
IEnumerable<IPaymentLinkExtension> paymentLinkExtensions,
BTCPayNetworkBase network)
BTCPayNetworkBase network,
PaymentMethodHandlerDictionary handlers)
{
PaymentMethodId = paymentMethodId;
_network = network;
_handlers = handlers;
paymentLinkExtension = paymentLinkExtensions.Single(p => p.PaymentMethodId == PaymentMethodId);
}
public PaymentMethodId PaymentMethodId { get; }
@@ -30,6 +35,18 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
{
if (context.Model.Activated)
{
if (_handlers.TryGetValue(PaymentMethodId, out var handler))
{
var details = context.InvoiceEntity.GetPayments(true)
.Select(p => p.GetDetails<MoneroLikePaymentData>(handler))
.Where(p => p is not null)
.FirstOrDefault();
if (details is not null)
{
context.Model.ReceivedConfirmations = details.ConfirmationCount;
context.Model.RequiredConfirmations = (int)MoneroListener.ConfirmationsRequired(details, context.InvoiceEntity.SpeedPolicy);
}
}
context.Model.InvoiceBitcoinUrl = paymentLinkExtension.GetPaymentLink(context.Prompt, context.UrlHelper);
context.Model.InvoiceBitcoinUrlQR = context.Model.InvoiceBitcoinUrl;
}

View File

@@ -369,34 +369,27 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
}
private bool GetStatus(MoneroLikePaymentData details, SpeedPolicy speedPolicy)
{
if (details.ConfirmationCount < details.LockTime)
{
return false;
}
if (details.InvoiceSettledConfirmationThreshold is { } v)
return details.ConfirmationCount >= v;
switch (speedPolicy)
{
case SpeedPolicy.HighSpeed:
return details.ConfirmationCount >= 0;
case SpeedPolicy.MediumSpeed:
return details.ConfirmationCount >= 1;
case SpeedPolicy.LowMediumSpeed:
return details.ConfirmationCount >= 2;
case SpeedPolicy.LowSpeed:
return details.ConfirmationCount >= 6;
default:
return false;
}
}
=> ConfirmationsRequired(details, speedPolicy) <= details.ConfirmationCount;
public static long ConfirmationsRequired(MoneroLikePaymentData details, SpeedPolicy speedPolicy)
=> (details, speedPolicy) switch
{
(_, _) when details.ConfirmationCount < details.LockTime => details.LockTime - details.ConfirmationCount,
({ InvoiceSettledConfirmationThreshold: long v }, _) => v,
(_, SpeedPolicy.HighSpeed) => 0,
(_, SpeedPolicy.MediumSpeed) => 1,
(_, SpeedPolicy.LowMediumSpeed) => 2,
(_, SpeedPolicy.LowSpeed) => 6,
_ => 6,
};
private async Task UpdateAnyPendingMoneroLikePayment(string cryptoCode)
{
var invoices = await _invoiceRepository.GetPendingInvoices();
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
var invoices = await _invoiceRepository.GetInvoicesWithPendingPayments(paymentMethodId);
if (!invoices.Any())
return;
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
invoices = invoices.Where(entity => entity.GetPaymentPrompt(paymentMethodId)?.Activated is true).ToArray();
await UpdatePaymentStates(cryptoCode, invoices);
}

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Payments;
using BTCPayServer.Services.Altcoins.Zcash.Services;
using BTCPayServer.Services.Invoices;
namespace BTCPayServer.Services.Altcoins.Zcash.Payments
@@ -8,15 +9,18 @@ namespace BTCPayServer.Services.Altcoins.Zcash.Payments
public class ZcashPaymentModelExtension : IPaymentModelExtension
{
private readonly BTCPayNetworkBase _network;
private readonly PaymentMethodHandlerDictionary _handlers;
private readonly IPaymentLinkExtension paymentLinkExtension;
public ZcashPaymentModelExtension(
PaymentMethodId paymentMethodId,
IEnumerable<IPaymentLinkExtension> paymentLinkExtensions,
BTCPayNetworkBase network)
BTCPayNetworkBase network,
PaymentMethodHandlerDictionary handlers)
{
PaymentMethodId = paymentMethodId;
_network = network;
_handlers = handlers;
paymentLinkExtension = paymentLinkExtensions.Single(p => p.PaymentMethodId == PaymentMethodId);
}
public PaymentMethodId PaymentMethodId { get; }
@@ -30,6 +34,18 @@ namespace BTCPayServer.Services.Altcoins.Zcash.Payments
{
if (context.Model.Activated)
{
if (_handlers.TryGetValue(PaymentMethodId, out var handler))
{
var details = context.InvoiceEntity.GetPayments(true)
.Select(p => p.GetDetails<ZcashLikePaymentData>(handler))
.Where(p => p is not null)
.FirstOrDefault();
if (details is not null)
{
context.Model.ReceivedConfirmations = details.ConfirmationCount;
context.Model.RequiredConfirmations = (int)ZcashListener.ConfirmationsRequired(context.InvoiceEntity.SpeedPolicy);
}
}
context.Model.InvoiceBitcoinUrl = paymentLinkExtension.GetPaymentLink(context.Prompt, context.UrlHelper);
context.Model.InvoiceBitcoinUrlQR = context.Model.InvoiceBitcoinUrl;
}

View File

@@ -363,28 +363,23 @@ namespace BTCPayServer.Services.Altcoins.Zcash.Services
}
private bool GetStatus(ZcashLikePaymentData details, SpeedPolicy speedPolicy)
=> details.ConfirmationCount >= ConfirmationsRequired(speedPolicy);
public static int ConfirmationsRequired(SpeedPolicy speedPolicy)
=> speedPolicy switch
{
switch (speedPolicy)
{
case SpeedPolicy.HighSpeed:
return details.ConfirmationCount >= 0;
case SpeedPolicy.MediumSpeed:
return details.ConfirmationCount >= 1;
case SpeedPolicy.LowMediumSpeed:
return details.ConfirmationCount >= 2;
case SpeedPolicy.LowSpeed:
return details.ConfirmationCount >= 6;
default:
return false;
}
}
SpeedPolicy.HighSpeed => 0,
SpeedPolicy.MediumSpeed => 1,
SpeedPolicy.LowMediumSpeed => 2,
SpeedPolicy.LowSpeed => 6,
_ => 6,
};
private async Task UpdateAnyPendingZcashLikePayment(string cryptoCode)
{
var invoices = await _invoiceRepository.GetPendingInvoices();
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
var invoices = await _invoiceRepository.GetInvoicesWithPendingPayments(paymentMethodId);
if (!invoices.Any())
return;
var paymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode);
invoices = invoices.Where(entity => entity.GetPaymentPrompt(paymentMethodId).Activated).ToArray();
await UpdatePaymentStates(cryptoCode, invoices);
}

View File

@@ -67,18 +67,6 @@ namespace BTCPayServer.Services.Invoices
};
}
public async Task<bool> RemovePendingInvoice(string invoiceId)
{
using var ctx = _applicationDbContextFactory.CreateContext();
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId });
try
{
await ctx.SaveChangesAsync();
return true;
}
catch (DbUpdateException) { return false; }
}
public async Task<IEnumerable<InvoiceEntity>> GetInvoicesFromAddresses(string[] addresses)
{
if (addresses.Length is 0)
@@ -103,23 +91,31 @@ namespace BTCPayServer.Services.Invoices
}
}
public async Task<InvoiceEntity[]> GetPendingInvoices(bool includeAddressData = false, bool skipNoPaymentInvoices = false, CancellationToken cancellationToken = default)
public async Task<InvoiceEntity[]> GetInvoicesWithPendingPayments(PaymentMethodId paymentMethodId, bool includeAddresses = false)
{
using var ctx = _applicationDbContextFactory.CreateContext();
var q = ctx.PendingInvoices.AsQueryable();
q = q.Include(o => o.InvoiceData)
.ThenInclude(o => o.Payments);
if (includeAddressData)
q = q.Include(o => o.InvoiceData)
.ThenInclude(o => o.AddressInvoices);
if (skipNoPaymentInvoices)
q = q.Where(i => i.InvoiceData.Payments.Any());
return (await q.Select(o => o.InvoiceData).ToArrayAsync(cancellationToken)).Select(ToEntity).ToArray();
var invoiceIds = (await ctx.Payments.Where(p => PaymentData.IsPending(p.Status)).Select(p => p.InvoiceDataId).ToArrayAsync()).Distinct().ToArray();
if (invoiceIds.Length is 0)
return Array.Empty<InvoiceEntity>();
return await GetInvoices(new InvoiceQuery()
{
InvoiceId = invoiceIds,
IncludeAddresses = true
});
}
public async Task<string[]> GetPendingInvoiceIds()
public async Task<InvoiceEntity[]> GetPendingInvoices(CancellationToken cancellationToken = default)
{
using var ctx = _applicationDbContextFactory.CreateContext();
return await ctx.PendingInvoices.AsQueryable().Select(data => data.Id).ToArrayAsync();
var rows = await ctx.Invoices.Where(i => InvoiceData.IsPending(i.Status))
.Include(i => i.Payments)
.Select(o => o).ToArrayAsync();
return rows.Select(ToEntity).ToArray();
}
public async Task<InvoiceEntity[]> GetPendingInvoices()
{
using var ctx = _applicationDbContextFactory.CreateContext();
return (await ctx.Invoices.Where(i => InvoiceData.IsPending(i.Status)).Select(i => i).ToArrayAsync())
.Select(i => ToEntity(i)).ToArray();
}
public async Task<List<Data.WebhookDeliveryData>> GetWebhookDeliveries(string invoiceId)
@@ -170,27 +166,6 @@ retry:
_eventAggregator.Publish(new InvoiceNeedUpdateEvent(invoiceId));
}
public async Task ExtendInvoiceMonitor(string invoiceId)
{
retry:
using (var ctx = _applicationDbContextFactory.CreateContext())
{
var invoiceData = await ctx.Invoices.FindAsync(invoiceId);
var invoice = invoiceData.GetBlob();
invoice.MonitoringExpiration = invoice.MonitoringExpiration.AddHours(1);
invoiceData.SetBlob(invoice);
try
{
await ctx.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
goto retry;
}
}
}
public async Task CreateInvoiceAsync(InvoiceCreationContext creationContext)
{
var invoice = creationContext.InvoiceEntity;
@@ -225,7 +200,6 @@ retry:
if (prompt.Activated)
textSearch.Add(prompt.Calculate().TotalDue.ToString());
}
await context.PendingInvoices.AddAsync(new PendingInvoiceData() { Id = invoice.Id });
textSearch.Add(invoice.Id);
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
@@ -357,20 +331,6 @@ retry:
}
}
public async Task AddPendingInvoiceIfNotPresent(string invoiceId)
{
using var context = _applicationDbContextFactory.CreateContext();
if (!context.PendingInvoices.Any(a => a.Id == invoiceId))
{
context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoiceId });
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateException) { } // Already exists
}
}
const string InsertInvoiceEvent = "INSERT INTO \"InvoiceEvents\" (\"InvoiceDataId\", \"Severity\", \"Message\", \"Timestamp\") VALUES (@InvoiceDataId, @Severity, @Message, @Timestamp)";
public async Task AddInvoiceEvent(string invoiceId, object evt, InvoiceEventData.EventSeverity severity)

View File

@@ -19,5 +19,11 @@ namespace BTCPayServer.Services.Invoices
Details = JToken.FromObject(details, handler.Serializer);
return this;
}
public T GetDetails<T>(IPaymentMethodHandler handler) where T : class
{
if (handler.Id != handler.Id)
return null;
return handler.ParsePaymentDetails(Details) as T;
}
}
}

View File

@@ -21,15 +21,10 @@
m.Crypto = network.CryptoCode;
m.DepositAddress = BitcoinAddress.Create(payment.Destination, network.NBitcoinNetwork);
var confirmationCount = onChainPaymentData.ConfirmationCount;
if (confirmationCount >= network.MaxTrackedConfirmation)
{
m.Confirmations = "At least " + (network.MaxTrackedConfirmation);
}
else
{
m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture);
}
var confReq = NBXplorerListener.ConfirmationRequired(payment.InvoiceEntity, onChainPaymentData);
var confCount = onChainPaymentData.ConfirmationCount;
confCount = Math.Min(confReq, confCount);
m.Confirmations = $"{confCount} / {confReq}";
if (onChainPaymentData.PayjoinInformation is PayjoinInformation pj)
{
payjoinInformation = pj;

View File

@@ -2,6 +2,7 @@
@using BTCPayServer.Plugins.Altcoins;
@using BTCPayServer.Services
@using BTCPayServer.Services.Altcoins.Monero.Payments
@using BTCPayServer.Services.Altcoins.Monero.Services
@using BTCPayServer.Services.Altcoins.Monero.UI
@using BTCPayServer.Services.Invoices
@inject DisplayFormatter DisplayFormatter
@@ -19,15 +20,11 @@
m.Crypto = handler.Network.CryptoCode;
m.DepositAddress = payment.Destination;
m.Amount = payment.Value.ToString(CultureInfo.InvariantCulture);
var confirmationCount = onChainPaymentData.ConfirmationCount;
if (confirmationCount >= handler.Network.MaxTrackedConfirmation)
{
m.Confirmations = "At least " + (handler.Network.MaxTrackedConfirmation);
}
else
{
m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture);
}
var confReq = MoneroListener.ConfirmationsRequired(onChainPaymentData, payment.InvoiceEntity.SpeedPolicy);
var confCount = onChainPaymentData.ConfirmationCount;
confCount = Math.Min(confReq, confCount);
m.Confirmations = $"{confCount} / {confReq}";
m.TransactionId = onChainPaymentData.TransactionId;
m.ReceivedTime = payment.ReceivedTime;

View File

@@ -3,6 +3,7 @@
@using BTCPayServer.Components.TruncateCenter
@using BTCPayServer.Services
@using BTCPayServer.Services.Altcoins.Zcash.Payments
@using BTCPayServer.Services.Altcoins.Zcash.Services
@using BTCPayServer.Services.Altcoins.Zcash.UI
@using BTCPayServer.Services.Invoices
@inject DisplayFormatter DisplayFormatter
@@ -20,15 +21,11 @@
m.Crypto = handler.Network.CryptoCode;
m.DepositAddress = payment.Destination;
m.Amount = payment.Value.ToString(CultureInfo.InvariantCulture);
var confirmationCount = onChainPaymentData.ConfirmationCount;
if (confirmationCount >= handler.Network.MaxTrackedConfirmation)
{
m.Confirmations = "At least " + (handler.Network.MaxTrackedConfirmation);
}
else
{
m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture);
}
var confReq = ZcashListener.ConfirmationsRequired(payment.InvoiceEntity.SpeedPolicy);
var confCount = onChainPaymentData.ConfirmationCount;
confCount = Math.Min(confReq, confCount);
m.Confirmations = $"{confCount} / {confReq}";
m.TransactionId = onChainPaymentData.TransactionId;
m.ReceivedTime = payment.ReceivedTime;