mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-02-17 20:24:28 +01:00
Merge pull request #1913 from Kukks/ln-gen-new-partial
Ensure new bolt11 invoice on partial payment to btcpay invoice
This commit is contained in:
@@ -747,6 +747,51 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(tor.Services.Where(t => t.ServiceType == TorServiceType.Other).Count() > 1);
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task EnsureNewLightningInvoiceOnPartialPayment()
|
||||
{
|
||||
using var tester = ServerTester.Create();
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
|
||||
user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.01m, "BTC"));
|
||||
await tester.WaitForEvent<InvoiceNewAddressEvent>(async () =>
|
||||
{
|
||||
await tester.ExplorerNode.SendToAddressAsync(
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest), Money.Coins(0.005m));
|
||||
});
|
||||
|
||||
var newInvoice = await user.BitPay.GetInvoiceAsync(invoice.Id);
|
||||
var newBolt11 = newInvoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
|
||||
var oldBolt11= invoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
|
||||
Assert.NotEqual(newBolt11,oldBolt11);
|
||||
Assert.Equal(newInvoice.BtcDue.GetValue(), BOLT11PaymentRequest.Parse(newBolt11, Network.RegTest).MinimumAmount.ToDecimal(LightMoneyUnit.BTC));
|
||||
var evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
{
|
||||
await tester.SendLightningPaymentAsync(newInvoice);
|
||||
});
|
||||
Assert.Equal(evt.InvoiceId, invoice.Id);
|
||||
Assert.Equal(InvoiceStatus.Complete, evt.State.Status);
|
||||
Assert.Equal(InvoiceExceptionStatus.None, evt.State.ExceptionStatus);
|
||||
|
||||
evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
{
|
||||
await tester.SendLightningPaymentAsync(invoice);
|
||||
});
|
||||
|
||||
Assert.Equal(evt.InvoiceId, invoice.Id);
|
||||
Assert.Equal(InvoiceStatus.Invalid, evt.State.Status);
|
||||
Assert.Equal(InvoiceExceptionStatus.PaidOver, evt.State.ExceptionStatus);
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
|
||||
@@ -47,8 +47,17 @@ namespace BTCPayServer.Payments.Lightning
|
||||
//direct casting to (BTCPayNetwork) is fixed in other pull requests with better generic interfacing for handlers
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var test = GetNodeInfo(paymentMethod.PreferOnion, supportedPaymentMethod, network);
|
||||
|
||||
var invoice = paymentMethod.ParentEntity;
|
||||
var due = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility);
|
||||
decimal due = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility);
|
||||
try
|
||||
{
|
||||
due = paymentMethod.Calculate().Due.ToDecimal(MoneyUnit.BTC);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
var client = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), network);
|
||||
var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow;
|
||||
if (expiry < TimeSpan.Zero)
|
||||
|
||||
@@ -5,11 +5,14 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -24,6 +27,8 @@ namespace BTCPayServer.Payments.Lightning
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
readonly BTCPayNetworkProvider _NetworkProvider;
|
||||
private readonly LightningClientFactoryService lightningClientFactory;
|
||||
private readonly LightningLikePaymentHandler _lightningLikePaymentHandler;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
readonly Channel<string> _CheckInvoices = Channel.CreateUnbounded<string>();
|
||||
Task _CheckingInvoice;
|
||||
readonly Dictionary<(string, string), LightningInstanceListener> _InstanceListeners = new Dictionary<(string, string), LightningInstanceListener>();
|
||||
@@ -32,13 +37,17 @@ namespace BTCPayServer.Payments.Lightning
|
||||
InvoiceRepository invoiceRepository,
|
||||
IMemoryCache memoryCache,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
LightningClientFactoryService lightningClientFactory)
|
||||
LightningClientFactoryService lightningClientFactory,
|
||||
LightningLikePaymentHandler lightningLikePaymentHandler,
|
||||
StoreRepository storeRepository)
|
||||
{
|
||||
_Aggregator = aggregator;
|
||||
_InvoiceRepository = invoiceRepository;
|
||||
_memoryCache = memoryCache;
|
||||
_NetworkProvider = networkProvider;
|
||||
this.lightningClientFactory = lightningClientFactory;
|
||||
_lightningLikePaymentHandler = lightningLikePaymentHandler;
|
||||
_storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
async Task CheckingInvoice(CancellationToken cancellation)
|
||||
@@ -54,7 +63,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
if (!_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener) ||
|
||||
!instanceListener.IsListening)
|
||||
{
|
||||
instanceListener = instanceListener ?? new LightningInstanceListener(_InvoiceRepository, _Aggregator, listenedInvoice.SupportedPaymentMethod, lightningClientFactory, listenedInvoice.Network);
|
||||
instanceListener ??= new LightningInstanceListener(_InvoiceRepository, _Aggregator, listenedInvoice.SupportedPaymentMethod, lightningClientFactory, listenedInvoice.Network);
|
||||
var status = await instanceListener.PollPayment(listenedInvoice, cancellation);
|
||||
if (status is null ||
|
||||
status is LightningInvoiceStatus.Paid ||
|
||||
@@ -134,6 +143,51 @@ namespace BTCPayServer.Payments.Lightning
|
||||
_CheckInvoices.Writer.TryWrite(inv.Invoice.Id);
|
||||
}
|
||||
}));
|
||||
leases.Add(_Aggregator.Subscribe<Events.InvoiceDataChangedEvent>(async inv =>
|
||||
{
|
||||
if (inv.State.Status == InvoiceStatus.New &&
|
||||
inv.State.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(inv.InvoiceId);
|
||||
var paymentMethods = invoice.GetPaymentMethods()
|
||||
.Where(method => method.GetId().PaymentType == PaymentTypes.LightningLike).ToArray();
|
||||
var store = await _storeRepository.FindStore(invoice.StoreId);
|
||||
if (paymentMethods.Any())
|
||||
{
|
||||
var logs = new InvoiceLogs();
|
||||
logs.Write("Partial payment detected, attempting to update all lightning payment methods with new bolt11 with correct due amount.", InvoiceEventData.EventSeverity.Info);
|
||||
foreach (var paymentMethod in paymentMethods)
|
||||
{
|
||||
try
|
||||
{
|
||||
var supportedMethod =
|
||||
invoice.GetSupportedPaymentMethod<LightningSupportedPaymentMethod>(
|
||||
paymentMethod.GetId()).First();
|
||||
var prepObj =
|
||||
_lightningLikePaymentHandler.PreparePayment(supportedMethod, store,
|
||||
paymentMethod.Network);
|
||||
var newPaymentMethodDetails =
|
||||
await _lightningLikePaymentHandler.CreatePaymentMethodDetails(
|
||||
logs, supportedMethod,
|
||||
paymentMethod, store, paymentMethod.Network, prepObj);
|
||||
|
||||
await _InvoiceRepository.NewAddress(invoice.Id, newPaymentMethodDetails,
|
||||
paymentMethod.Network);
|
||||
|
||||
_Aggregator.Publish(new InvoiceNewAddressEvent(invoice.Id, newPaymentMethodDetails.GetPaymentDestination(),paymentMethod.Network ));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logs.Write($"Could not update {paymentMethod.GetId().ToPrettyString()}: {e.Message}",
|
||||
InvoiceEventData.EventSeverity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
await _InvoiceRepository.AddInvoiceLogs(invoice.Id, logs);
|
||||
_CheckInvoices.Writer.TryWrite(inv.InvoiceId);
|
||||
}
|
||||
}
|
||||
}));
|
||||
_CheckingInvoice = CheckingInvoice(_Cts.Token);
|
||||
_ListenPoller = new Timer(async s =>
|
||||
{
|
||||
@@ -168,7 +222,6 @@ namespace BTCPayServer.Payments.Lightning
|
||||
}
|
||||
|
||||
readonly CancellationTokenSource _Cts = new CancellationTokenSource();
|
||||
readonly HashSet<string> _InvoiceIds = new HashSet<string>();
|
||||
private Timer _ListenPoller;
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
|
||||
Reference in New Issue
Block a user