mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 14:04:26 +01:00
Payment Settled Webhook event (#2944)
* Payment Settled Webhook event resolves #2691 * Move payment methods to payment services
This commit is contained in:
@@ -11,6 +11,7 @@ namespace BTCPayServer.Client.Models
|
|||||||
InvoiceProcessing,
|
InvoiceProcessing,
|
||||||
InvoiceExpired,
|
InvoiceExpired,
|
||||||
InvoiceSettled,
|
InvoiceSettled,
|
||||||
InvoiceInvalid
|
InvoiceInvalid,
|
||||||
|
InvoicePaymentSettled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,72 +10,88 @@ namespace BTCPayServer.Client.Models
|
|||||||
{
|
{
|
||||||
public WebhookInvoiceEvent()
|
public WebhookInvoiceEvent()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebhookInvoiceEvent(WebhookEventType evtType)
|
public WebhookInvoiceEvent(WebhookEventType evtType)
|
||||||
{
|
{
|
||||||
this.Type = evtType;
|
this.Type = evtType;
|
||||||
}
|
}
|
||||||
[JsonProperty(Order = 1)]
|
|
||||||
public string StoreId { get; set; }
|
[JsonProperty(Order = 1)] public string StoreId { get; set; }
|
||||||
[JsonProperty(Order = 2)]
|
[JsonProperty(Order = 2)] public string InvoiceId { get; set; }
|
||||||
public string InvoiceId { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class WebhookInvoiceSettledEvent : WebhookInvoiceEvent
|
public class WebhookInvoiceSettledEvent : WebhookInvoiceEvent
|
||||||
{
|
{
|
||||||
public WebhookInvoiceSettledEvent()
|
public WebhookInvoiceSettledEvent()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebhookInvoiceSettledEvent(WebhookEventType evtType) : base(evtType)
|
public WebhookInvoiceSettledEvent(WebhookEventType evtType) : base(evtType)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ManuallyMarked { get; set; }
|
public bool ManuallyMarked { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class WebhookInvoiceInvalidEvent : WebhookInvoiceEvent
|
public class WebhookInvoiceInvalidEvent : WebhookInvoiceEvent
|
||||||
{
|
{
|
||||||
public WebhookInvoiceInvalidEvent()
|
public WebhookInvoiceInvalidEvent()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebhookInvoiceInvalidEvent(WebhookEventType evtType) : base(evtType)
|
public WebhookInvoiceInvalidEvent(WebhookEventType evtType) : base(evtType)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ManuallyMarked { get; set; }
|
public bool ManuallyMarked { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class WebhookInvoiceProcessingEvent : WebhookInvoiceEvent
|
public class WebhookInvoiceProcessingEvent : WebhookInvoiceEvent
|
||||||
{
|
{
|
||||||
public WebhookInvoiceProcessingEvent()
|
public WebhookInvoiceProcessingEvent()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebhookInvoiceProcessingEvent(WebhookEventType evtType) : base(evtType)
|
public WebhookInvoiceProcessingEvent(WebhookEventType evtType) : base(evtType)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OverPaid { get; set; }
|
public bool OverPaid { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class WebhookInvoiceReceivedPaymentEvent : WebhookInvoiceEvent
|
public class WebhookInvoiceReceivedPaymentEvent : WebhookInvoiceEvent
|
||||||
{
|
{
|
||||||
public WebhookInvoiceReceivedPaymentEvent()
|
public WebhookInvoiceReceivedPaymentEvent()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebhookInvoiceReceivedPaymentEvent(WebhookEventType evtType) : base(evtType)
|
public WebhookInvoiceReceivedPaymentEvent(WebhookEventType evtType) : base(evtType)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AfterExpiration { get; set; }
|
public bool AfterExpiration { get; set; }
|
||||||
|
public string PaymentMethod { get; set; }
|
||||||
|
public InvoicePaymentMethodDataModel.Payment Payment { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class WebhookInvoicePaymentSettledEvent : WebhookInvoiceReceivedPaymentEvent
|
||||||
|
{
|
||||||
|
public WebhookInvoicePaymentSettledEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebhookInvoicePaymentSettledEvent(WebhookEventType evtType) : base(evtType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class WebhookInvoiceExpiredEvent : WebhookInvoiceEvent
|
public class WebhookInvoiceExpiredEvent : WebhookInvoiceEvent
|
||||||
{
|
{
|
||||||
public WebhookInvoiceExpiredEvent()
|
public WebhookInvoiceExpiredEvent()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebhookInvoiceExpiredEvent(WebhookEventType evtType) : base(evtType)
|
public WebhookInvoiceExpiredEvent(WebhookEventType evtType) : base(evtType)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3119,6 +3119,20 @@ namespace BTCPayServer.Tests
|
|||||||
c =>
|
c =>
|
||||||
{
|
{
|
||||||
Assert.False(c.AfterExpiration);
|
Assert.False(c.AfterExpiration);
|
||||||
|
Assert.Equal(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(),c.PaymentMethod);
|
||||||
|
Assert.NotNull(c.Payment);
|
||||||
|
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
|
||||||
|
Assert.StartsWith(txId.ToString(), c.Payment.Id);
|
||||||
|
|
||||||
|
});
|
||||||
|
user.AssertHasWebhookEvent<WebhookInvoicePaymentSettledEvent>(WebhookEventType.InvoicePaymentSettled,
|
||||||
|
c =>
|
||||||
|
{
|
||||||
|
Assert.False(c.AfterExpiration);
|
||||||
|
Assert.Equal(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(),c.PaymentMethod);
|
||||||
|
Assert.NotNull(c.Payment);
|
||||||
|
Assert.Equal(invoice.BitcoinAddress, c.Payment.Destination);
|
||||||
|
Assert.StartsWith(txId.ToString(), c.Payment.Id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -363,27 +363,28 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
PaymentLink =
|
PaymentLink =
|
||||||
method.GetId().PaymentType.GetPaymentLink(method.Network, details, accounting.Due,
|
method.GetId().PaymentType.GetPaymentLink(method.Network, details, accounting.Due,
|
||||||
Request.GetAbsoluteRoot()),
|
Request.GetAbsoluteRoot()),
|
||||||
Payments = payments.Select(paymentEntity =>
|
Payments = payments.Select(paymentEntity => ToPaymentModel(entity, paymentEntity)).ToList()
|
||||||
{
|
|
||||||
var data = paymentEntity.GetCryptoPaymentData();
|
|
||||||
return new InvoicePaymentMethodDataModel.Payment()
|
|
||||||
{
|
|
||||||
Destination = data.GetDestination(),
|
|
||||||
Id = data.GetPaymentId(),
|
|
||||||
Status = !paymentEntity.Accounted
|
|
||||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid
|
|
||||||
: data.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) ||
|
|
||||||
data.PaymentCompleted(paymentEntity)
|
|
||||||
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Settled
|
|
||||||
: InvoicePaymentMethodDataModel.Payment.PaymentStatus.Processing,
|
|
||||||
Fee = paymentEntity.NetworkFee,
|
|
||||||
Value = data.GetValue(),
|
|
||||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime
|
|
||||||
};
|
|
||||||
}).ToList()
|
|
||||||
};
|
};
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static InvoicePaymentMethodDataModel.Payment ToPaymentModel(InvoiceEntity entity, PaymentEntity paymentEntity)
|
||||||
|
{
|
||||||
|
var data = paymentEntity.GetCryptoPaymentData();
|
||||||
|
return new InvoicePaymentMethodDataModel.Payment()
|
||||||
|
{
|
||||||
|
Destination = data.GetDestination(),
|
||||||
|
Id = data.GetPaymentId(),
|
||||||
|
Status = !paymentEntity.Accounted
|
||||||
|
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Invalid
|
||||||
|
: data.PaymentConfirmed(paymentEntity, entity.SpeedPolicy) || data.PaymentCompleted(paymentEntity)
|
||||||
|
? InvoicePaymentMethodDataModel.Payment.PaymentStatus.Settled
|
||||||
|
: InvoicePaymentMethodDataModel.Payment.PaymentStatus.Processing,
|
||||||
|
Fee = paymentEntity.NetworkFee,
|
||||||
|
Value = data.GetValue(),
|
||||||
|
ReceivedDate = paymentEntity.ReceivedTime.DateTime
|
||||||
|
};
|
||||||
|
}
|
||||||
private InvoiceData ToModel(InvoiceEntity entity)
|
private InvoiceData ToModel(InvoiceEntity entity)
|
||||||
{
|
{
|
||||||
return new InvoiceData()
|
return new InvoiceData()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace BTCPayServer.Events
|
|||||||
{
|
{
|
||||||
Created = 1001,
|
Created = 1001,
|
||||||
ReceivedPayment = 1002,
|
ReceivedPayment = 1002,
|
||||||
|
PaymentSettled = 1014,
|
||||||
PaidInFull = 1003,
|
PaidInFull = 1003,
|
||||||
Expired = 1004,
|
Expired = 1004,
|
||||||
Confirmed = 1005,
|
Confirmed = 1005,
|
||||||
@@ -21,6 +22,7 @@ namespace BTCPayServer.Events
|
|||||||
{
|
{
|
||||||
public const string Created = "invoice_created";
|
public const string Created = "invoice_created";
|
||||||
public const string ReceivedPayment = "invoice_receivedPayment";
|
public const string ReceivedPayment = "invoice_receivedPayment";
|
||||||
|
public const string PaymentSettled = "invoice_paymentSettled";
|
||||||
public const string MarkedCompleted = "invoice_markedComplete";
|
public const string MarkedCompleted = "invoice_markedComplete";
|
||||||
public const string MarkedInvalid = "invoice_markedInvalid";
|
public const string MarkedInvalid = "invoice_markedInvalid";
|
||||||
public const string Expired = "invoice_expired";
|
public const string Expired = "invoice_expired";
|
||||||
@@ -36,6 +38,7 @@ namespace BTCPayServer.Events
|
|||||||
{
|
{
|
||||||
{Created, InvoiceEventCode.Created},
|
{Created, InvoiceEventCode.Created},
|
||||||
{ReceivedPayment, InvoiceEventCode.ReceivedPayment},
|
{ReceivedPayment, InvoiceEventCode.ReceivedPayment},
|
||||||
|
{PaymentSettled, InvoiceEventCode.PaymentSettled},
|
||||||
{PaidInFull, InvoiceEventCode.PaidInFull},
|
{PaidInFull, InvoiceEventCode.PaidInFull},
|
||||||
{Expired, InvoiceEventCode.Expired},
|
{Expired, InvoiceEventCode.Expired},
|
||||||
{Confirmed, InvoiceEventCode.Confirmed},
|
{Confirmed, InvoiceEventCode.Confirmed},
|
||||||
|
|||||||
@@ -311,6 +311,11 @@ namespace BTCPayServer.HostedServices
|
|||||||
{
|
{
|
||||||
leases.Add(_EventAggregator.Subscribe<InvoiceEvent>(async e =>
|
leases.Add(_EventAggregator.Subscribe<InvoiceEvent>(async e =>
|
||||||
{
|
{
|
||||||
|
if (e.EventCode == InvoiceEventCode.PaymentSettled)
|
||||||
|
{
|
||||||
|
//these are greenfield specific events
|
||||||
|
return;
|
||||||
|
}
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(e.Invoice.Id);
|
var invoice = await _InvoiceRepository.GetInvoice(e.Invoice.Id);
|
||||||
if (invoice == null)
|
if (invoice == null)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -52,21 +52,24 @@ namespace BTCPayServer.HostedServices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly InvoiceRepository _InvoiceRepository;
|
readonly InvoiceRepository _invoiceRepository;
|
||||||
readonly EventAggregator _EventAggregator;
|
readonly EventAggregator _eventAggregator;
|
||||||
readonly ExplorerClientProvider _ExplorerClientProvider;
|
readonly ExplorerClientProvider _explorerClientProvider;
|
||||||
private readonly NotificationSender _notificationSender;
|
private readonly NotificationSender _notificationSender;
|
||||||
|
private readonly PaymentService _paymentService;
|
||||||
|
|
||||||
public InvoiceWatcher(
|
public InvoiceWatcher(
|
||||||
InvoiceRepository invoiceRepository,
|
InvoiceRepository invoiceRepository,
|
||||||
EventAggregator eventAggregator,
|
EventAggregator eventAggregator,
|
||||||
ExplorerClientProvider explorerClientProvider,
|
ExplorerClientProvider explorerClientProvider,
|
||||||
NotificationSender notificationSender)
|
NotificationSender notificationSender,
|
||||||
|
PaymentService paymentService)
|
||||||
{
|
{
|
||||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
_invoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||||
_EventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||||
_ExplorerClientProvider = explorerClientProvider;
|
_explorerClientProvider = explorerClientProvider;
|
||||||
_notificationSender = notificationSender;
|
_notificationSender = notificationSender;
|
||||||
|
_paymentService = paymentService;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly CompositeDisposable leases = new CompositeDisposable();
|
readonly CompositeDisposable leases = new CompositeDisposable();
|
||||||
@@ -239,7 +242,7 @@ namespace BTCPayServer.HostedServices
|
|||||||
|
|
||||||
private async Task Wait(string invoiceId)
|
private async Task Wait(string invoiceId)
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
var invoice = await _invoiceRepository.GetInvoice(invoiceId);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// add 1 second to ensure watch won't trigger moments before invoice expires
|
// add 1 second to ensure watch won't trigger moments before invoice expires
|
||||||
@@ -274,11 +277,11 @@ namespace BTCPayServer.HostedServices
|
|||||||
_Loop = StartLoop(_Cts.Token);
|
_Loop = StartLoop(_Cts.Token);
|
||||||
_ = WaitPendingInvoices();
|
_ = WaitPendingInvoices();
|
||||||
|
|
||||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceNeedUpdateEvent>(b =>
|
leases.Add(_eventAggregator.Subscribe<Events.InvoiceNeedUpdateEvent>(b =>
|
||||||
{
|
{
|
||||||
Watch(b.InvoiceId);
|
Watch(b.InvoiceId);
|
||||||
}));
|
}));
|
||||||
leases.Add(_EventAggregator.Subscribe<Events.InvoiceEvent>(async b =>
|
leases.Add(_eventAggregator.Subscribe<Events.InvoiceEvent>(async b =>
|
||||||
{
|
{
|
||||||
if (InvoiceEventNotification.HandlesEvent(b.Name))
|
if (InvoiceEventNotification.HandlesEvent(b.Name))
|
||||||
{
|
{
|
||||||
@@ -301,7 +304,7 @@ namespace BTCPayServer.HostedServices
|
|||||||
|
|
||||||
private async Task WaitPendingInvoices()
|
private async Task WaitPendingInvoices()
|
||||||
{
|
{
|
||||||
await Task.WhenAll((await _InvoiceRepository.GetPendingInvoices())
|
await Task.WhenAll((await _invoiceRepository.GetPendingInvoices())
|
||||||
.Select(id => Wait(id)).ToArray());
|
.Select(id => Wait(id)).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,28 +321,28 @@ namespace BTCPayServer.HostedServices
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
cancellation.ThrowIfCancellationRequested();
|
cancellation.ThrowIfCancellationRequested();
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId, true);
|
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||||
if (invoice == null)
|
if (invoice == null)
|
||||||
break;
|
break;
|
||||||
var updateContext = new UpdateInvoiceContext(invoice);
|
var updateContext = new UpdateInvoiceContext(invoice);
|
||||||
UpdateInvoice(updateContext);
|
UpdateInvoice(updateContext);
|
||||||
if (updateContext.Unaffect)
|
if (updateContext.Unaffect)
|
||||||
{
|
{
|
||||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
await _invoiceRepository.UnaffectAddress(invoice.Id);
|
||||||
}
|
}
|
||||||
if (updateContext.Dirty)
|
if (updateContext.Dirty)
|
||||||
{
|
{
|
||||||
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.GetInvoiceState());
|
await _invoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.GetInvoiceState());
|
||||||
updateContext.Events.Insert(0, new InvoiceDataChangedEvent(invoice));
|
updateContext.Events.Insert(0, new InvoiceDataChangedEvent(invoice));
|
||||||
}
|
}
|
||||||
if (updateContext.IsBlobUpdated)
|
if (updateContext.IsBlobUpdated)
|
||||||
{
|
{
|
||||||
await _InvoiceRepository.UpdateInvoicePrice(invoice.Id, invoice);
|
await _invoiceRepository.UpdateInvoicePrice(invoice.Id, invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var evt in updateContext.Events)
|
foreach (var evt in updateContext.Events)
|
||||||
{
|
{
|
||||||
_EventAggregator.Publish(evt, evt.GetType());
|
_eventAggregator.Publish(evt, evt.GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invoice.Status == InvoiceStatusLegacy.Complete ||
|
if (invoice.Status == InvoiceStatusLegacy.Complete ||
|
||||||
@@ -351,11 +354,11 @@ namespace BTCPayServer.HostedServices
|
|||||||
// say user used low fee and we only got 3 confirmations right before it's time to remove
|
// say user used low fee and we only got 3 confirmations right before it's time to remove
|
||||||
if (extendInvoiceMonitoring)
|
if (extendInvoiceMonitoring)
|
||||||
{
|
{
|
||||||
await _InvoiceRepository.ExtendInvoiceMonitor(invoice.Id);
|
await _invoiceRepository.ExtendInvoiceMonitor(invoice.Id);
|
||||||
}
|
}
|
||||||
else if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id))
|
else if (await _invoiceRepository.RemovePendingInvoice(invoice.Id))
|
||||||
{
|
{
|
||||||
_EventAggregator.Publish(new InvoiceStopWatchedEvent(invoice.Id));
|
_eventAggregator.Publish(new InvoiceStopWatchedEvent(invoice.Id));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -389,7 +392,7 @@ namespace BTCPayServer.HostedServices
|
|||||||
if ((onChainPaymentData.ConfirmationCount < network.MaxTrackedConfirmation && payment.Accounted)
|
if ((onChainPaymentData.ConfirmationCount < network.MaxTrackedConfirmation && payment.Accounted)
|
||||||
&& (onChainPaymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
|
&& (onChainPaymentData.Legacy || invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
|
||||||
{
|
{
|
||||||
var transactionResult = await _ExplorerClientProvider.GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(onChainPaymentData.Outpoint.Hash);
|
var transactionResult = await _explorerClientProvider.GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(onChainPaymentData.Outpoint.Hash);
|
||||||
var confirmationCount = transactionResult?.Confirmations ?? 0;
|
var confirmationCount = transactionResult?.Confirmations ?? 0;
|
||||||
onChainPaymentData.ConfirmationCount = confirmationCount;
|
onChainPaymentData.ConfirmationCount = confirmationCount;
|
||||||
payment.SetCryptoPaymentData(onChainPaymentData);
|
payment.SetCryptoPaymentData(onChainPaymentData);
|
||||||
@@ -408,7 +411,7 @@ namespace BTCPayServer.HostedServices
|
|||||||
var updatedPaymentData = updateConfirmationCountIfNeeded.Where(a => a.Result != null).Select(a => a.Result).ToList();
|
var updatedPaymentData = updateConfirmationCountIfNeeded.Where(a => a.Result != null).Select(a => a.Result).ToList();
|
||||||
if (updatedPaymentData.Count > 0)
|
if (updatedPaymentData.Count > 0)
|
||||||
{
|
{
|
||||||
await _InvoiceRepository.UpdatePayments(updatedPaymentData);
|
await _paymentService.UpdatePayments(updatedPaymentData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return extendInvoiceMonitoring;
|
return extendInvoiceMonitoring;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Threading;
|
|||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Controllers.GreenField;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Logging;
|
using BTCPayServer.Logging;
|
||||||
@@ -183,6 +184,8 @@ namespace BTCPayServer.HostedServices
|
|||||||
return new WebhookInvoiceEvent(WebhookEventType.InvoiceCreated);
|
return new WebhookInvoiceEvent(WebhookEventType.InvoiceCreated);
|
||||||
case WebhookEventType.InvoiceReceivedPayment:
|
case WebhookEventType.InvoiceReceivedPayment:
|
||||||
return new WebhookInvoiceReceivedPaymentEvent(WebhookEventType.InvoiceReceivedPayment);
|
return new WebhookInvoiceReceivedPaymentEvent(WebhookEventType.InvoiceReceivedPayment);
|
||||||
|
case WebhookEventType.InvoicePaymentSettled:
|
||||||
|
return new WebhookInvoicePaymentSettledEvent(WebhookEventType.InvoicePaymentSettled);
|
||||||
case WebhookEventType.InvoiceProcessing:
|
case WebhookEventType.InvoiceProcessing:
|
||||||
return new WebhookInvoiceProcessingEvent(WebhookEventType.InvoiceProcessing);
|
return new WebhookInvoiceProcessingEvent(WebhookEventType.InvoiceProcessing);
|
||||||
case WebhookEventType.InvoiceExpired:
|
case WebhookEventType.InvoiceExpired:
|
||||||
@@ -232,7 +235,16 @@ namespace BTCPayServer.HostedServices
|
|||||||
case InvoiceEventCode.ReceivedPayment:
|
case InvoiceEventCode.ReceivedPayment:
|
||||||
return new WebhookInvoiceReceivedPaymentEvent(WebhookEventType.InvoiceReceivedPayment)
|
return new WebhookInvoiceReceivedPaymentEvent(WebhookEventType.InvoiceReceivedPayment)
|
||||||
{
|
{
|
||||||
AfterExpiration = invoiceEvent.Invoice.Status.ToModernStatus() == InvoiceStatus.Expired || invoiceEvent.Invoice.Status.ToModernStatus() == InvoiceStatus.Invalid
|
AfterExpiration = invoiceEvent.Invoice.Status.ToModernStatus() == InvoiceStatus.Expired || invoiceEvent.Invoice.Status.ToModernStatus() == InvoiceStatus.Invalid,
|
||||||
|
PaymentMethod = invoiceEvent.Payment.GetPaymentMethodId().ToStringNormalized(),
|
||||||
|
Payment = GreenFieldInvoiceController.ToPaymentModel(invoiceEvent.Invoice, invoiceEvent.Payment)
|
||||||
|
};
|
||||||
|
case InvoiceEventCode.PaymentSettled:
|
||||||
|
return new WebhookInvoiceReceivedPaymentEvent(WebhookEventType.InvoicePaymentSettled)
|
||||||
|
{
|
||||||
|
AfterExpiration = invoiceEvent.Invoice.Status.ToModernStatus() == InvoiceStatus.Expired || invoiceEvent.Invoice.Status.ToModernStatus() == InvoiceStatus.Invalid,
|
||||||
|
PaymentMethod = invoiceEvent.Payment.GetPaymentMethodId().ToStringNormalized(),
|
||||||
|
Payment = GreenFieldInvoiceController.ToPaymentModel(invoiceEvent.Invoice, invoiceEvent.Payment)
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -102,11 +102,8 @@ namespace BTCPayServer.Hosting
|
|||||||
services.AddStartupTask<MigrationStartupTask>();
|
services.AddStartupTask<MigrationStartupTask>();
|
||||||
//
|
//
|
||||||
services.AddStartupTask<BlockExplorerLinkStartupTask>();
|
services.AddStartupTask<BlockExplorerLinkStartupTask>();
|
||||||
services.TryAddSingleton<InvoiceRepository>(o =>
|
services.TryAddSingleton<InvoiceRepository>();
|
||||||
{
|
services.AddSingleton<PaymentService>();
|
||||||
var dbContext = o.GetRequiredService<ApplicationDbContextFactory>();
|
|
||||||
return new InvoiceRepository(dbContext, o.GetRequiredService<BTCPayNetworkProvider>(), o.GetService<EventAggregator>());
|
|
||||||
});
|
|
||||||
services.AddSingleton<BTCPayServerEnvironment>();
|
services.AddSingleton<BTCPayServerEnvironment>();
|
||||||
services.TryAddSingleton<TokenRepository>();
|
services.TryAddSingleton<TokenRepository>();
|
||||||
services.TryAddSingleton<WalletRepository>();
|
services.TryAddSingleton<WalletRepository>();
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
readonly EventAggregator _Aggregator;
|
readonly EventAggregator _Aggregator;
|
||||||
private readonly PayJoinRepository _payJoinRepository;
|
private readonly PayJoinRepository _payJoinRepository;
|
||||||
readonly ExplorerClientProvider _ExplorerClients;
|
readonly ExplorerClientProvider _ExplorerClients;
|
||||||
readonly IHostApplicationLifetime _Lifetime;
|
private readonly PaymentService _paymentService;
|
||||||
readonly InvoiceRepository _InvoiceRepository;
|
readonly InvoiceRepository _InvoiceRepository;
|
||||||
private TaskCompletionSource<bool> _RunningTask;
|
private TaskCompletionSource<bool> _RunningTask;
|
||||||
private CancellationTokenSource _Cts;
|
private CancellationTokenSource _Cts;
|
||||||
@@ -39,7 +39,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
InvoiceRepository invoiceRepository,
|
InvoiceRepository invoiceRepository,
|
||||||
EventAggregator aggregator,
|
EventAggregator aggregator,
|
||||||
PayJoinRepository payjoinRepository,
|
PayJoinRepository payjoinRepository,
|
||||||
IHostApplicationLifetime lifetime)
|
PaymentService paymentService)
|
||||||
{
|
{
|
||||||
PollInterval = TimeSpan.FromMinutes(1.0);
|
PollInterval = TimeSpan.FromMinutes(1.0);
|
||||||
_Wallets = wallets;
|
_Wallets = wallets;
|
||||||
@@ -47,7 +47,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
_ExplorerClients = explorerClients;
|
_ExplorerClients = explorerClients;
|
||||||
_Aggregator = aggregator;
|
_Aggregator = aggregator;
|
||||||
_payJoinRepository = payjoinRepository;
|
_payJoinRepository = payjoinRepository;
|
||||||
_Lifetime = lifetime;
|
_paymentService = paymentService;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly CompositeDisposable leases = new CompositeDisposable();
|
readonly CompositeDisposable leases = new CompositeDisposable();
|
||||||
@@ -167,7 +167,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
.GetAllBitcoinPaymentData(false).Any(c => c.GetPaymentId() == paymentData.GetPaymentId());
|
.GetAllBitcoinPaymentData(false).Any(c => c.GetPaymentId() == paymentData.GetPaymentId());
|
||||||
if (!alreadyExist)
|
if (!alreadyExist)
|
||||||
{
|
{
|
||||||
var payment = await _InvoiceRepository.AddPayment(invoice.Id,
|
var payment = await _paymentService.AddPayment(invoice.Id,
|
||||||
DateTimeOffset.UtcNow, paymentData, network);
|
DateTimeOffset.UtcNow, paymentData, network);
|
||||||
if (payment != null)
|
if (payment != null)
|
||||||
await ReceivedPayment(wallet, invoice, payment,
|
await ReceivedPayment(wallet, invoice, payment,
|
||||||
@@ -341,7 +341,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
await _payJoinRepository.TryUnlock(payjoinInformation.ContributedOutPoints);
|
await _payJoinRepository.TryUnlock(payjoinInformation.ContributedOutPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _InvoiceRepository.UpdatePayments(updatedPaymentEntities);
|
await _paymentService.UpdatePayments(updatedPaymentEntities);
|
||||||
if (updatedPaymentEntities.Count != 0)
|
if (updatedPaymentEntities.Count != 0)
|
||||||
_Aggregator.Publish(new Events.InvoiceNeedUpdateEvent(invoice.Id));
|
_Aggregator.Publish(new Events.InvoiceNeedUpdateEvent(invoice.Id));
|
||||||
return invoice;
|
return invoice;
|
||||||
@@ -383,7 +383,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
|||||||
var paymentData = new BitcoinLikePaymentData(address, coin.Value, coin.OutPoint,
|
var paymentData = new BitcoinLikePaymentData(address, coin.Value, coin.OutPoint,
|
||||||
transaction?.Transaction is null ? true : transaction.Transaction.RBF, coin.KeyPath);
|
transaction?.Transaction is null ? true : transaction.Transaction.RBF, coin.KeyPath);
|
||||||
|
|
||||||
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.Timestamp, paymentData, network).ConfigureAwait(false);
|
var payment = await _paymentService.AddPayment(invoice.Id, coin.Timestamp, paymentData, network).ConfigureAwait(false);
|
||||||
alreadyAccounted.Add(coin.OutPoint);
|
alreadyAccounted.Add(coin.OutPoint);
|
||||||
if (payment != null)
|
if (payment != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
private readonly LightningClientFactoryService lightningClientFactory;
|
private readonly LightningClientFactoryService lightningClientFactory;
|
||||||
private readonly LightningLikePaymentHandler _lightningLikePaymentHandler;
|
private readonly LightningLikePaymentHandler _lightningLikePaymentHandler;
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
|
private readonly PaymentService _paymentService;
|
||||||
readonly Channel<string> _CheckInvoices = Channel.CreateUnbounded<string>();
|
readonly Channel<string> _CheckInvoices = Channel.CreateUnbounded<string>();
|
||||||
Task _CheckingInvoice;
|
Task _CheckingInvoice;
|
||||||
readonly Dictionary<(string, string), LightningInstanceListener> _InstanceListeners = new Dictionary<(string, string), LightningInstanceListener>();
|
readonly Dictionary<(string, string), LightningInstanceListener> _InstanceListeners = new Dictionary<(string, string), LightningInstanceListener>();
|
||||||
@@ -42,7 +43,8 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
LightningClientFactoryService lightningClientFactory,
|
LightningClientFactoryService lightningClientFactory,
|
||||||
LightningLikePaymentHandler lightningLikePaymentHandler,
|
LightningLikePaymentHandler lightningLikePaymentHandler,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
IOptions<LightningNetworkOptions> options)
|
IOptions<LightningNetworkOptions> options,
|
||||||
|
PaymentService paymentService)
|
||||||
{
|
{
|
||||||
_Aggregator = aggregator;
|
_Aggregator = aggregator;
|
||||||
_InvoiceRepository = invoiceRepository;
|
_InvoiceRepository = invoiceRepository;
|
||||||
@@ -51,6 +53,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
this.lightningClientFactory = lightningClientFactory;
|
this.lightningClientFactory = lightningClientFactory;
|
||||||
_lightningLikePaymentHandler = lightningLikePaymentHandler;
|
_lightningLikePaymentHandler = lightningLikePaymentHandler;
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
|
_paymentService = paymentService;
|
||||||
Options = options;
|
Options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +70,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
if (!_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener) ||
|
if (!_InstanceListeners.TryGetValue(instanceListenerKey, out var instanceListener) ||
|
||||||
!instanceListener.IsListening)
|
!instanceListener.IsListening)
|
||||||
{
|
{
|
||||||
instanceListener ??= new LightningInstanceListener(_InvoiceRepository, _Aggregator, lightningClientFactory, listenedInvoice.Network, GetLightningUrl(listenedInvoice.SupportedPaymentMethod));
|
instanceListener ??= new LightningInstanceListener(_InvoiceRepository, _Aggregator, lightningClientFactory, listenedInvoice.Network, GetLightningUrl(listenedInvoice.SupportedPaymentMethod), _paymentService);
|
||||||
var status = await instanceListener.PollPayment(listenedInvoice, cancellation);
|
var status = await instanceListener.PollPayment(listenedInvoice, cancellation);
|
||||||
if (status is null ||
|
if (status is null ||
|
||||||
status is LightningInvoiceStatus.Paid ||
|
status is LightningInvoiceStatus.Paid ||
|
||||||
@@ -309,9 +312,10 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
|
|
||||||
public class LightningInstanceListener
|
public class LightningInstanceListener
|
||||||
{
|
{
|
||||||
private readonly InvoiceRepository invoiceRepository;
|
private readonly InvoiceRepository _invoiceRepository;
|
||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
private readonly BTCPayNetwork network;
|
private readonly BTCPayNetwork _network;
|
||||||
|
private readonly PaymentService _paymentService;
|
||||||
private readonly LightningClientFactoryService _lightningClientFactory;
|
private readonly LightningClientFactoryService _lightningClientFactory;
|
||||||
|
|
||||||
public LightningConnectionString ConnectionString { get; }
|
public LightningConnectionString ConnectionString { get; }
|
||||||
@@ -320,13 +324,15 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
EventAggregator eventAggregator,
|
EventAggregator eventAggregator,
|
||||||
LightningClientFactoryService lightningClientFactory,
|
LightningClientFactoryService lightningClientFactory,
|
||||||
BTCPayNetwork network,
|
BTCPayNetwork network,
|
||||||
LightningConnectionString connectionString)
|
LightningConnectionString connectionString,
|
||||||
|
PaymentService paymentService)
|
||||||
{
|
{
|
||||||
if (connectionString == null)
|
if (connectionString == null)
|
||||||
throw new ArgumentNullException(nameof(connectionString));
|
throw new ArgumentNullException(nameof(connectionString));
|
||||||
this.invoiceRepository = invoiceRepository;
|
this._invoiceRepository = invoiceRepository;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
this.network = network;
|
this._network = network;
|
||||||
|
_paymentService = paymentService;
|
||||||
_lightningClientFactory = lightningClientFactory;
|
_lightningClientFactory = lightningClientFactory;
|
||||||
ConnectionString = connectionString;
|
ConnectionString = connectionString;
|
||||||
}
|
}
|
||||||
@@ -337,12 +343,12 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
|
|
||||||
internal async Task<LightningInvoiceStatus?> PollPayment(ListenedInvoice listenedInvoice, CancellationToken cancellation)
|
internal async Task<LightningInvoiceStatus?> PollPayment(ListenedInvoice listenedInvoice, CancellationToken cancellation)
|
||||||
{
|
{
|
||||||
var client = _lightningClientFactory.Create(ConnectionString, network);
|
var client = _lightningClientFactory.Create(ConnectionString, _network);
|
||||||
LightningInvoice lightningInvoice = await client.GetInvoice(listenedInvoice.PaymentMethodDetails.InvoiceId);
|
LightningInvoice lightningInvoice = await client.GetInvoice(listenedInvoice.PaymentMethodDetails.InvoiceId);
|
||||||
if (lightningInvoice?.Status is LightningInvoiceStatus.Paid &&
|
if (lightningInvoice?.Status is LightningInvoiceStatus.Paid &&
|
||||||
await AddPayment(lightningInvoice, listenedInvoice.InvoiceId))
|
await AddPayment(lightningInvoice, listenedInvoice.InvoiceId))
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): Payment detected via polling on {listenedInvoice.InvoiceId}");
|
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Payment detected via polling on {listenedInvoice.InvoiceId}");
|
||||||
}
|
}
|
||||||
return lightningInvoice?.Status;
|
return lightningInvoice?.Status;
|
||||||
}
|
}
|
||||||
@@ -360,17 +366,17 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
public CancellationTokenSource StopListeningCancellationTokenSource;
|
public CancellationTokenSource StopListeningCancellationTokenSource;
|
||||||
async Task Listen(CancellationToken cancellation)
|
async Task Listen(CancellationToken cancellation)
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): Start listening {ConnectionString.BaseUri}");
|
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Start listening {ConnectionString.BaseUri}");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var lightningClient = _lightningClientFactory.Create(ConnectionString, network);
|
var lightningClient = _lightningClientFactory.Create(ConnectionString, _network);
|
||||||
using (var session = await lightningClient.Listen(cancellation))
|
using (var session = await lightningClient.Listen(cancellation))
|
||||||
{
|
{
|
||||||
// Just in case the payment arrived after our last poll but before we listened.
|
// Just in case the payment arrived after our last poll but before we listened.
|
||||||
await PollAllListenedInvoices(cancellation);
|
await PollAllListenedInvoices(cancellation);
|
||||||
if (_ErrorAlreadyLogged)
|
if (_ErrorAlreadyLogged)
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): Could reconnect successfully to {ConnectionString.BaseUri}");
|
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Could reconnect successfully to {ConnectionString.BaseUri}");
|
||||||
}
|
}
|
||||||
_ErrorAlreadyLogged = false;
|
_ErrorAlreadyLogged = false;
|
||||||
while (!_ListenedInvoices.IsEmpty)
|
while (!_ListenedInvoices.IsEmpty)
|
||||||
@@ -386,7 +392,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
{
|
{
|
||||||
if (await AddPayment(notification, listenedInvoice.InvoiceId))
|
if (await AddPayment(notification, listenedInvoice.InvoiceId))
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): Payment detected via notification ({listenedInvoice.InvoiceId})");
|
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Payment detected via notification ({listenedInvoice.InvoiceId})");
|
||||||
}
|
}
|
||||||
_ListenedInvoices.TryRemove(notification.Id, out var _);
|
_ListenedInvoices.TryRemove(notification.Id, out var _);
|
||||||
}
|
}
|
||||||
@@ -401,12 +407,12 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
catch (Exception ex) when (!cancellation.IsCancellationRequested && !_ErrorAlreadyLogged)
|
catch (Exception ex) when (!cancellation.IsCancellationRequested && !_ErrorAlreadyLogged)
|
||||||
{
|
{
|
||||||
_ErrorAlreadyLogged = true;
|
_ErrorAlreadyLogged = true;
|
||||||
Logs.PayServer.LogError(ex, $"{network.CryptoCode} (Lightning): Error while contacting {ConnectionString.BaseUri}");
|
Logs.PayServer.LogError(ex, $"{_network.CryptoCode} (Lightning): Error while contacting {ConnectionString.BaseUri}");
|
||||||
Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): Stop listening {ConnectionString.BaseUri}");
|
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Stop listening {ConnectionString.BaseUri}");
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { }
|
catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { }
|
||||||
if (_ListenedInvoices.IsEmpty)
|
if (_ListenedInvoices.IsEmpty)
|
||||||
Logs.PayServer.LogInformation($"{network.CryptoCode} (Lightning): No more invoice to listen on {ConnectionString.BaseUri}, releasing the connection.");
|
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): No more invoice to listen on {ConnectionString.BaseUri}, releasing the connection.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeOffset? LastFullPoll { get; set; }
|
public DateTimeOffset? LastFullPoll { get; set; }
|
||||||
@@ -433,15 +439,15 @@ namespace BTCPayServer.Payments.Lightning
|
|||||||
|
|
||||||
public async Task<bool> AddPayment(LightningInvoice notification, string invoiceId)
|
public async Task<bool> AddPayment(LightningInvoice notification, string invoiceId)
|
||||||
{
|
{
|
||||||
var payment = await invoiceRepository.AddPayment(invoiceId, notification.PaidAt.Value, new LightningLikePaymentData()
|
var payment = await _paymentService.AddPayment(invoiceId, notification.PaidAt.Value, new LightningLikePaymentData()
|
||||||
{
|
{
|
||||||
BOLT11 = notification.BOLT11,
|
BOLT11 = notification.BOLT11,
|
||||||
PaymentHash = BOLT11PaymentRequest.Parse(notification.BOLT11, network.NBitcoinNetwork).PaymentHash,
|
PaymentHash = BOLT11PaymentRequest.Parse(notification.BOLT11, _network.NBitcoinNetwork).PaymentHash,
|
||||||
Amount = notification.AmountReceived ?? notification.Amount, // if running old version amount received might be unavailable
|
Amount = notification.AmountReceived ?? notification.Amount, // if running old version amount received might be unavailable
|
||||||
}, network, accounted: true);
|
}, _network, accounted: true);
|
||||||
if (payment != null)
|
if (payment != null)
|
||||||
{
|
{
|
||||||
var invoice = await invoiceRepository.GetInvoice(invoiceId);
|
var invoice = await _invoiceRepository.GetInvoice(invoiceId);
|
||||||
if (invoice != null)
|
if (invoice != null)
|
||||||
_eventAggregator.Publish(new InvoiceEvent(invoice, InvoiceEvent.ReceivedPayment) { Payment = payment });
|
_eventAggregator.Publish(new InvoiceEvent(invoice, InvoiceEvent.ReceivedPayment) { Payment = payment });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ namespace BTCPayServer.Payments.PayJoin
|
|||||||
private readonly BTCPayServerEnvironment _env;
|
private readonly BTCPayServerEnvironment _env;
|
||||||
private readonly WalletReceiveService _walletReceiveService;
|
private readonly WalletReceiveService _walletReceiveService;
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
|
private readonly PaymentService _paymentService;
|
||||||
|
|
||||||
public PayJoinEndpointController(BTCPayNetworkProvider btcPayNetworkProvider,
|
public PayJoinEndpointController(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||||
InvoiceRepository invoiceRepository, ExplorerClientProvider explorerClientProvider,
|
InvoiceRepository invoiceRepository, ExplorerClientProvider explorerClientProvider,
|
||||||
@@ -101,7 +102,8 @@ namespace BTCPayServer.Payments.PayJoin
|
|||||||
DelayedTransactionBroadcaster broadcaster,
|
DelayedTransactionBroadcaster broadcaster,
|
||||||
BTCPayServerEnvironment env,
|
BTCPayServerEnvironment env,
|
||||||
WalletReceiveService walletReceiveService,
|
WalletReceiveService walletReceiveService,
|
||||||
StoreRepository storeRepository)
|
StoreRepository storeRepository,
|
||||||
|
PaymentService paymentService)
|
||||||
{
|
{
|
||||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||||
_invoiceRepository = invoiceRepository;
|
_invoiceRepository = invoiceRepository;
|
||||||
@@ -114,6 +116,7 @@ namespace BTCPayServer.Payments.PayJoin
|
|||||||
_env = env;
|
_env = env;
|
||||||
_walletReceiveService = walletReceiveService;
|
_walletReceiveService = walletReceiveService;
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
|
_paymentService = paymentService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("")]
|
[HttpPost("")]
|
||||||
@@ -487,7 +490,7 @@ namespace BTCPayServer.Payments.PayJoin
|
|||||||
};
|
};
|
||||||
if (invoice != null)
|
if (invoice != null)
|
||||||
{
|
{
|
||||||
var payment = await _invoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, originalPaymentData, network, true);
|
var payment = await _paymentService.AddPayment(invoice.Id, DateTimeOffset.UtcNow, originalPaymentData, network, true);
|
||||||
if (payment is null)
|
if (payment is null)
|
||||||
{
|
{
|
||||||
return UnprocessableEntity(CreatePayjoinError("already-paid",
|
return UnprocessableEntity(CreatePayjoinError("already-paid",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
|||||||
private readonly SettingsRepository _settingsRepository;
|
private readonly SettingsRepository _settingsRepository;
|
||||||
private readonly InvoiceRepository _invoiceRepository;
|
private readonly InvoiceRepository _invoiceRepository;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly PaymentService _paymentService;
|
||||||
private readonly Dictionary<int, EthereumWatcher> _chainHostedServices = new Dictionary<int, EthereumWatcher>();
|
private readonly Dictionary<int, EthereumWatcher> _chainHostedServices = new Dictionary<int, EthereumWatcher>();
|
||||||
|
|
||||||
private readonly Dictionary<int, CancellationTokenSource> _chainHostedServiceCancellationTokenSources =
|
private readonly Dictionary<int, CancellationTokenSource> _chainHostedServiceCancellationTokenSources =
|
||||||
@@ -41,7 +42,8 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
|||||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||||
SettingsRepository settingsRepository,
|
SettingsRepository settingsRepository,
|
||||||
InvoiceRepository invoiceRepository,
|
InvoiceRepository invoiceRepository,
|
||||||
IConfiguration configuration) : base(
|
IConfiguration configuration,
|
||||||
|
PaymentService paymentService) : base(
|
||||||
eventAggregator)
|
eventAggregator)
|
||||||
{
|
{
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
@@ -51,6 +53,7 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
|||||||
_settingsRepository = settingsRepository;
|
_settingsRepository = settingsRepository;
|
||||||
_invoiceRepository = invoiceRepository;
|
_invoiceRepository = invoiceRepository;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
_paymentService = paymentService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task StartAsync(CancellationToken cancellationToken)
|
public override async Task StartAsync(CancellationToken cancellationToken)
|
||||||
@@ -186,7 +189,7 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
|||||||
_chainHostedServiceCancellationTokenSources.AddOrReplace(ethereumLikeConfiguration.ChainId, cts);
|
_chainHostedServiceCancellationTokenSources.AddOrReplace(ethereumLikeConfiguration.ChainId, cts);
|
||||||
_chainHostedServices.AddOrReplace(ethereumLikeConfiguration.ChainId,
|
_chainHostedServices.AddOrReplace(ethereumLikeConfiguration.ChainId,
|
||||||
new EthereumWatcher(ethereumLikeConfiguration.ChainId, ethereumLikeConfiguration,
|
new EthereumWatcher(ethereumLikeConfiguration.ChainId, ethereumLikeConfiguration,
|
||||||
_btcPayNetworkProvider, _eventAggregator, _invoiceRepository));
|
_btcPayNetworkProvider, _eventAggregator, _invoiceRepository, _paymentService));
|
||||||
await _chainHostedServices[ethereumLikeConfiguration.ChainId].StartAsync(CancellationTokenSource
|
await _chainHostedServices[ethereumLikeConfiguration.ChainId].StartAsync(CancellationTokenSource
|
||||||
.CreateLinkedTokenSource(cancellationToken, cts.Token).Token);
|
.CreateLinkedTokenSource(cancellationToken, cts.Token).Token);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
|||||||
{
|
{
|
||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
private readonly InvoiceRepository _invoiceRepository;
|
private readonly InvoiceRepository _invoiceRepository;
|
||||||
|
private readonly PaymentService _paymentService;
|
||||||
private int ChainId { get; }
|
private int ChainId { get; }
|
||||||
private readonly HashSet<PaymentMethodId> PaymentMethods;
|
private readonly HashSet<PaymentMethodId> PaymentMethods;
|
||||||
|
|
||||||
@@ -113,7 +114,7 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
|||||||
AccountIndex = response.PaymentMethodDetails.Index,
|
AccountIndex = response.PaymentMethodDetails.Index,
|
||||||
XPub = response.PaymentMethodDetails.XPub
|
XPub = response.PaymentMethodDetails.XPub
|
||||||
};
|
};
|
||||||
var payment = await _invoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow,
|
var payment = await _paymentService.AddPayment(invoice.Id, DateTimeOffset.UtcNow,
|
||||||
paymentData, network, true);
|
paymentData, network, true);
|
||||||
if (payment != null) ReceivedPayment(invoice, payment);
|
if (payment != null) ReceivedPayment(invoice, payment);
|
||||||
}
|
}
|
||||||
@@ -125,7 +126,7 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
|||||||
{
|
{
|
||||||
existingPayment.Accounted = false;
|
existingPayment.Accounted = false;
|
||||||
|
|
||||||
await _invoiceRepository.UpdatePayments(new List<PaymentEntity>() {existingPayment});
|
await _paymentService.UpdatePayments(new List<PaymentEntity>() {existingPayment});
|
||||||
if (response.Amount > 0)
|
if (response.Amount > 0)
|
||||||
{
|
{
|
||||||
var paymentData = new EthereumLikePaymentData()
|
var paymentData = new EthereumLikePaymentData()
|
||||||
@@ -148,7 +149,7 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
|||||||
AccountIndex = cd.AccountIndex,
|
AccountIndex = cd.AccountIndex,
|
||||||
XPub = cd.XPub
|
XPub = cd.XPub
|
||||||
};
|
};
|
||||||
var payment = await _invoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow,
|
var payment = await _paymentService.AddPayment(invoice.Id, DateTimeOffset.UtcNow,
|
||||||
paymentData, network, true);
|
paymentData, network, true);
|
||||||
if (payment != null) ReceivedPayment(invoice, payment);
|
if (payment != null) ReceivedPayment(invoice, payment);
|
||||||
}
|
}
|
||||||
@@ -163,7 +164,7 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
|||||||
cd.BlockNumber = (long?)response.BlockParameter.BlockNumber.Value;
|
cd.BlockNumber = (long?)response.BlockParameter.BlockNumber.Value;
|
||||||
|
|
||||||
existingPayment.SetCryptoPaymentData(cd);
|
existingPayment.SetCryptoPaymentData(cd);
|
||||||
await _invoiceRepository.UpdatePayments(new List<PaymentEntity>() {existingPayment});
|
await _paymentService.UpdatePayments(new List<PaymentEntity>() {existingPayment});
|
||||||
|
|
||||||
_eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(invoice.Id));
|
_eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(invoice.Id));
|
||||||
}
|
}
|
||||||
@@ -183,7 +184,7 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
existingPayment.SetCryptoPaymentData(cd);
|
existingPayment.SetCryptoPaymentData(cd);
|
||||||
await _invoiceRepository.UpdatePayments(new List<PaymentEntity>() {existingPayment});
|
await _paymentService.UpdatePayments(new List<PaymentEntity>() {existingPayment});
|
||||||
|
|
||||||
_eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(invoice.Id));
|
_eventAggregator.Publish(new Events.InvoiceNeedUpdateEvent(invoice.Id));
|
||||||
}
|
}
|
||||||
@@ -345,11 +346,12 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Services
|
|||||||
|
|
||||||
public EthereumWatcher(int chainId, EthereumLikeConfiguration config,
|
public EthereumWatcher(int chainId, EthereumLikeConfiguration config,
|
||||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||||
EventAggregator eventAggregator, InvoiceRepository invoiceRepository) :
|
EventAggregator eventAggregator, InvoiceRepository invoiceRepository, PaymentService paymentService) :
|
||||||
base(eventAggregator)
|
base(eventAggregator)
|
||||||
{
|
{
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_invoiceRepository = invoiceRepository;
|
_invoiceRepository = invoiceRepository;
|
||||||
|
_paymentService = paymentService;
|
||||||
ChainId = chainId;
|
ChainId = chainId;
|
||||||
AuthenticationHeaderValue headerValue = null;
|
AuthenticationHeaderValue headerValue = null;
|
||||||
if (!string.IsNullOrEmpty(config.Web3ProviderUsername))
|
if (!string.IsNullOrEmpty(config.Web3ProviderUsername))
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
|
|||||||
private readonly MoneroLikeConfiguration _MoneroLikeConfiguration;
|
private readonly MoneroLikeConfiguration _MoneroLikeConfiguration;
|
||||||
private readonly BTCPayNetworkProvider _networkProvider;
|
private readonly BTCPayNetworkProvider _networkProvider;
|
||||||
private readonly ILogger<MoneroListener> _logger;
|
private readonly ILogger<MoneroListener> _logger;
|
||||||
|
private readonly PaymentService _paymentService;
|
||||||
private readonly CompositeDisposable leases = new CompositeDisposable();
|
private readonly CompositeDisposable leases = new CompositeDisposable();
|
||||||
private readonly Queue<Func<CancellationToken, Task>> taskQueue = new Queue<Func<CancellationToken, Task>>();
|
private readonly Queue<Func<CancellationToken, Task>> taskQueue = new Queue<Func<CancellationToken, Task>>();
|
||||||
private CancellationTokenSource _Cts;
|
private CancellationTokenSource _Cts;
|
||||||
@@ -36,7 +37,8 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
|
|||||||
MoneroRPCProvider moneroRpcProvider,
|
MoneroRPCProvider moneroRpcProvider,
|
||||||
MoneroLikeConfiguration moneroLikeConfiguration,
|
MoneroLikeConfiguration moneroLikeConfiguration,
|
||||||
BTCPayNetworkProvider networkProvider,
|
BTCPayNetworkProvider networkProvider,
|
||||||
ILogger<MoneroListener> logger)
|
ILogger<MoneroListener> logger,
|
||||||
|
PaymentService paymentService)
|
||||||
{
|
{
|
||||||
_invoiceRepository = invoiceRepository;
|
_invoiceRepository = invoiceRepository;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
@@ -44,6 +46,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
|
|||||||
_MoneroLikeConfiguration = moneroLikeConfiguration;
|
_MoneroLikeConfiguration = moneroLikeConfiguration;
|
||||||
_networkProvider = networkProvider;
|
_networkProvider = networkProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_paymentService = paymentService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
@@ -243,7 +246,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
transferProcessingTasks.Add(
|
transferProcessingTasks.Add(
|
||||||
_invoiceRepository.UpdatePayments(updatedPaymentEntities.Select(tuple => tuple.Item1).ToList()));
|
_paymentService.UpdatePayments(updatedPaymentEntities.Select(tuple => tuple.Item1).ToList()));
|
||||||
await Task.WhenAll(transferProcessingTasks);
|
await Task.WhenAll(transferProcessingTasks);
|
||||||
foreach (var valueTuples in updatedPaymentEntities.GroupBy(entity => entity.Item2))
|
foreach (var valueTuples in updatedPaymentEntities.GroupBy(entity => entity.Item2))
|
||||||
{
|
{
|
||||||
@@ -304,7 +307,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
|
|||||||
|
|
||||||
if (paymentsToUpdate.Any())
|
if (paymentsToUpdate.Any())
|
||||||
{
|
{
|
||||||
await _invoiceRepository.UpdatePayments(paymentsToUpdate.Select(tuple => tuple.Payment).ToList());
|
await _paymentService.UpdatePayments(paymentsToUpdate.Select(tuple => tuple.Payment).ToList());
|
||||||
foreach (var valueTuples in paymentsToUpdate.GroupBy(entity => entity.invoice))
|
foreach (var valueTuples in paymentsToUpdate.GroupBy(entity => entity.invoice))
|
||||||
{
|
{
|
||||||
if (valueTuples.Any())
|
if (valueTuples.Any())
|
||||||
@@ -341,7 +344,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
|
|||||||
//if it doesnt, add it and assign a new monerolike address to the system if a balance is still due
|
//if it doesnt, add it and assign a new monerolike address to the system if a balance is still due
|
||||||
if (alreadyExistingPaymentThatMatches.Payment == null)
|
if (alreadyExistingPaymentThatMatches.Payment == null)
|
||||||
{
|
{
|
||||||
var payment = await _invoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow,
|
var payment = await _paymentService.AddPayment(invoice.Id, DateTimeOffset.UtcNow,
|
||||||
paymentData, _networkProvider.GetNetwork<MoneroLikeSpecificBtcPayNetwork>(cryptoCode), true);
|
paymentData, _networkProvider.GetNetwork<MoneroLikeSpecificBtcPayNetwork>(cryptoCode), true);
|
||||||
if (payment != null)
|
if (payment != null)
|
||||||
await ReceivedPayment(invoice, payment);
|
await ReceivedPayment(invoice, payment);
|
||||||
|
|||||||
@@ -29,21 +29,21 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializerSettings);
|
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(DefaultSerializerSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ApplicationDbContextFactory _ContextFactory;
|
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
private readonly BTCPayNetworkProvider _Networks;
|
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||||
|
|
||||||
public InvoiceRepository(ApplicationDbContextFactory contextFactory,
|
public InvoiceRepository(ApplicationDbContextFactory contextFactory,
|
||||||
BTCPayNetworkProvider networks, EventAggregator eventAggregator)
|
BTCPayNetworkProvider networks, EventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
_ContextFactory = contextFactory;
|
_applicationDbContextFactory = contextFactory;
|
||||||
_Networks = networks;
|
_btcPayNetworkProvider = networks;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Data.WebhookDeliveryData> GetWebhookDelivery(string invoiceId, string deliveryId)
|
public async Task<Data.WebhookDeliveryData> GetWebhookDelivery(string invoiceId, string deliveryId)
|
||||||
{
|
{
|
||||||
using var ctx = _ContextFactory.CreateContext();
|
using var ctx = _applicationDbContextFactory.CreateContext();
|
||||||
return await ctx.InvoiceWebhookDeliveries
|
return await ctx.InvoiceWebhookDeliveries
|
||||||
.Where(d => d.InvoiceId == invoiceId && d.DeliveryId == deliveryId)
|
.Where(d => d.InvoiceId == invoiceId && d.DeliveryId == deliveryId)
|
||||||
.Select(d => d.Delivery)
|
.Select(d => d.Delivery)
|
||||||
@@ -54,7 +54,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
{
|
{
|
||||||
return new InvoiceEntity()
|
return new InvoiceEntity()
|
||||||
{
|
{
|
||||||
Networks = _Networks,
|
Networks = _btcPayNetworkProvider,
|
||||||
Version = InvoiceEntity.Lastest_Version,
|
Version = InvoiceEntity.Lastest_Version,
|
||||||
InvoiceTime = DateTimeOffset.UtcNow,
|
InvoiceTime = DateTimeOffset.UtcNow,
|
||||||
Metadata = new InvoiceMetadata()
|
Metadata = new InvoiceMetadata()
|
||||||
@@ -64,7 +64,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
public async Task<bool> RemovePendingInvoice(string invoiceId)
|
public async Task<bool> RemovePendingInvoice(string invoiceId)
|
||||||
{
|
{
|
||||||
Logs.PayServer.LogInformation($"Remove pending invoice {invoiceId}");
|
Logs.PayServer.LogInformation($"Remove pending invoice {invoiceId}");
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId });
|
ctx.PendingInvoices.Remove(new PendingInvoiceData() { Id = invoiceId });
|
||||||
try
|
try
|
||||||
@@ -78,7 +78,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task<IEnumerable<InvoiceEntity>> GetInvoicesFromAddresses(string[] addresses)
|
public async Task<IEnumerable<InvoiceEntity>> GetInvoicesFromAddresses(string[] addresses)
|
||||||
{
|
{
|
||||||
using (var db = _ContextFactory.CreateContext())
|
using (var db = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
return (await db.AddressInvoices
|
return (await db.AddressInvoices
|
||||||
.Include(a => a.InvoiceData.Payments)
|
.Include(a => a.InvoiceData.Payments)
|
||||||
@@ -92,7 +92,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task<string[]> GetPendingInvoices()
|
public async Task<string[]> GetPendingInvoices()
|
||||||
{
|
{
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
return await ctx.PendingInvoices.AsQueryable().Select(data => data.Id).ToArrayAsync();
|
return await ctx.PendingInvoices.AsQueryable().Select(data => data.Id).ToArrayAsync();
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task<List<Data.WebhookDeliveryData>> GetWebhookDeliveries(string invoiceId)
|
public async Task<List<Data.WebhookDeliveryData>> GetWebhookDeliveries(string invoiceId)
|
||||||
{
|
{
|
||||||
using var ctx = _ContextFactory.CreateContext();
|
using var ctx = _applicationDbContextFactory.CreateContext();
|
||||||
return await ctx.InvoiceWebhookDeliveries
|
return await ctx.InvoiceWebhookDeliveries
|
||||||
.Where(s => s.InvoiceId == invoiceId)
|
.Where(s => s.InvoiceId == invoiceId)
|
||||||
.Select(s => s.Delivery)
|
.Select(s => s.Delivery)
|
||||||
@@ -112,7 +112,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
{
|
{
|
||||||
if (storeId == null)
|
if (storeId == null)
|
||||||
throw new ArgumentNullException(nameof(storeId));
|
throw new ArgumentNullException(nameof(storeId));
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
return await ctx.Apps.Where(a => a.StoreDataId == storeId && a.TagAllInvoices).ToArrayAsync();
|
return await ctx.Apps.Where(a => a.StoreDataId == storeId && a.TagAllInvoices).ToArrayAsync();
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task UpdateInvoice(string invoiceId, UpdateCustomerModel data)
|
public async Task UpdateInvoice(string invoiceId, UpdateCustomerModel data)
|
||||||
{
|
{
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = await ctx.Invoices.FindAsync(invoiceId).ConfigureAwait(false);
|
var invoiceData = await ctx.Invoices.FindAsync(invoiceId).ConfigureAwait(false);
|
||||||
if (invoiceData == null)
|
if (invoiceData == null)
|
||||||
@@ -136,11 +136,11 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task ExtendInvoiceMonitor(string invoiceId)
|
public async Task ExtendInvoiceMonitor(string invoiceId)
|
||||||
{
|
{
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
using (var ctx = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = await ctx.Invoices.FindAsync(invoiceId);
|
var invoiceData = await ctx.Invoices.FindAsync(invoiceId);
|
||||||
|
|
||||||
var invoice = invoiceData.GetBlob(_Networks);
|
var invoice = invoiceData.GetBlob(_btcPayNetworkProvider);
|
||||||
invoice.MonitoringExpiration = invoice.MonitoringExpiration.AddHours(1);
|
invoice.MonitoringExpiration = invoice.MonitoringExpiration.AddHours(1);
|
||||||
invoiceData.Blob = ToBytes(invoice, null);
|
invoiceData.Blob = ToBytes(invoice, null);
|
||||||
|
|
||||||
@@ -152,13 +152,13 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
{
|
{
|
||||||
var textSearch = new HashSet<string>();
|
var textSearch = new HashSet<string>();
|
||||||
invoice = Clone(invoice);
|
invoice = Clone(invoice);
|
||||||
invoice.Networks = _Networks;
|
invoice.Networks = _btcPayNetworkProvider;
|
||||||
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
invoice.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
invoice.Payments = new List<PaymentEntity>();
|
invoice.Payments = new List<PaymentEntity>();
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
invoice.StoreId = storeId;
|
invoice.StoreId = storeId;
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = new Data.InvoiceData()
|
var invoiceData = new Data.InvoiceData()
|
||||||
{
|
{
|
||||||
@@ -229,12 +229,12 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
{
|
{
|
||||||
var temp = new InvoiceData();
|
var temp = new InvoiceData();
|
||||||
temp.Blob = ToBytes(invoice);
|
temp.Blob = ToBytes(invoice);
|
||||||
return temp.GetBlob(_Networks);
|
return temp.GetBlob(_btcPayNetworkProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddInvoiceLogs(string invoiceId, InvoiceLogs logs)
|
public async Task AddInvoiceLogs(string invoiceId, InvoiceLogs logs)
|
||||||
{
|
{
|
||||||
await using var context = _ContextFactory.CreateContext();
|
await using var context = _applicationDbContextFactory.CreateContext();
|
||||||
foreach (var log in logs.ToList())
|
foreach (var log in logs.ToList())
|
||||||
{
|
{
|
||||||
await context.InvoiceEvents.AddAsync(new InvoiceEventData()
|
await context.InvoiceEvents.AddAsync(new InvoiceEventData()
|
||||||
@@ -269,12 +269,12 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task<bool> NewPaymentDetails(string invoiceId, IPaymentMethodDetails paymentMethodDetails, BTCPayNetworkBase network)
|
public async Task<bool> NewPaymentDetails(string invoiceId, IPaymentMethodDetails paymentMethodDetails, BTCPayNetworkBase network)
|
||||||
{
|
{
|
||||||
await using var context = _ContextFactory.CreateContext();
|
await using var context = _applicationDbContextFactory.CreateContext();
|
||||||
var invoice = (await context.Invoices.Where(i => i.Id == invoiceId).ToListAsync()).FirstOrDefault();
|
var invoice = (await context.Invoices.Where(i => i.Id == invoiceId).ToListAsync()).FirstOrDefault();
|
||||||
if (invoice == null)
|
if (invoice == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var invoiceEntity = invoice.GetBlob(_Networks);
|
var invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
|
||||||
var paymentMethod = invoiceEntity.GetPaymentMethod(network, paymentMethodDetails.GetPaymentType());
|
var paymentMethod = invoiceEntity.GetPaymentMethod(network, paymentMethodDetails.GetPaymentType());
|
||||||
if (paymentMethod == null)
|
if (paymentMethod == null)
|
||||||
return false;
|
return false;
|
||||||
@@ -313,13 +313,13 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task UpdateInvoicePaymentMethod(string invoiceId, PaymentMethod paymentMethod)
|
public async Task UpdateInvoicePaymentMethod(string invoiceId, PaymentMethod paymentMethod)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoice = await context.Invoices.FindAsync(invoiceId);
|
var invoice = await context.Invoices.FindAsync(invoiceId);
|
||||||
if (invoice == null)
|
if (invoice == null)
|
||||||
return;
|
return;
|
||||||
var network = paymentMethod.Network;
|
var network = paymentMethod.Network;
|
||||||
var invoiceEntity = invoice.GetBlob(_Networks);
|
var invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
|
||||||
var newDetails = paymentMethod.GetPaymentMethodDetails();
|
var newDetails = paymentMethod.GetPaymentMethodDetails();
|
||||||
var existing = invoiceEntity.GetPaymentMethod(paymentMethod.GetId());
|
var existing = invoiceEntity.GetPaymentMethod(paymentMethod.GetId());
|
||||||
if (existing.GetPaymentMethodDetails().GetPaymentDestination() != newDetails.GetPaymentDestination() && newDetails.Activated)
|
if (existing.GetPaymentMethodDetails().GetPaymentDestination() != newDetails.GetPaymentDestination() && newDetails.Activated)
|
||||||
@@ -346,7 +346,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task AddPendingInvoiceIfNotPresent(string invoiceId)
|
public async Task AddPendingInvoiceIfNotPresent(string invoiceId)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
if (!context.PendingInvoices.Any(a => a.Id == invoiceId))
|
if (!context.PendingInvoices.Any(a => a.Id == invoiceId))
|
||||||
{
|
{
|
||||||
@@ -362,7 +362,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task AddInvoiceEvent(string invoiceId, object evt, InvoiceEventData.EventSeverity severity)
|
public async Task AddInvoiceEvent(string invoiceId, object evt, InvoiceEventData.EventSeverity severity)
|
||||||
{
|
{
|
||||||
await using var context = _ContextFactory.CreateContext();
|
await using var context = _applicationDbContextFactory.CreateContext();
|
||||||
await context.InvoiceEvents.AddAsync(new InvoiceEventData()
|
await context.InvoiceEvents.AddAsync(new InvoiceEventData()
|
||||||
{
|
{
|
||||||
Severity = severity,
|
Severity = severity,
|
||||||
@@ -396,7 +396,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task UnaffectAddress(string invoiceId)
|
public async Task UnaffectAddress(string invoiceId)
|
||||||
{
|
{
|
||||||
await using var context = _ContextFactory.CreateContext();
|
await using var context = _applicationDbContextFactory.CreateContext();
|
||||||
MarkUnassigned(invoiceId, context, null);
|
MarkUnassigned(invoiceId, context, null);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -416,7 +416,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState)
|
public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||||
if (invoiceData == null)
|
if (invoiceData == null)
|
||||||
@@ -430,12 +430,12 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
{
|
{
|
||||||
if (invoice.Type != InvoiceType.TopUp)
|
if (invoice.Type != InvoiceType.TopUp)
|
||||||
throw new ArgumentException("The invoice type should be TopUp to be able to update invoice price", nameof(invoice));
|
throw new ArgumentException("The invoice type should be TopUp to be able to update invoice price", nameof(invoice));
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
var invoiceData = await context.FindAsync<Data.InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||||
if (invoiceData == null)
|
if (invoiceData == null)
|
||||||
return;
|
return;
|
||||||
var blob = invoiceData.GetBlob(_Networks);
|
var blob = invoiceData.GetBlob(_btcPayNetworkProvider);
|
||||||
blob.Price = invoice.Price;
|
blob.Price = invoice.Price;
|
||||||
AddToTextSearch(context, invoiceData, new[] { invoice.Price.ToString(CultureInfo.InvariantCulture) });
|
AddToTextSearch(context, invoiceData, new[] { invoice.Price.ToString(CultureInfo.InvariantCulture) });
|
||||||
invoiceData.Blob = ToBytes(blob, null);
|
invoiceData.Blob = ToBytes(blob, null);
|
||||||
@@ -445,7 +445,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task MassArchive(string[] invoiceIds)
|
public async Task MassArchive(string[] invoiceIds)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var items = context.Invoices.Where(a => invoiceIds.Contains(a.Id));
|
var items = context.Invoices.Where(a => invoiceIds.Contains(a.Id));
|
||||||
if (items == null)
|
if (items == null)
|
||||||
@@ -464,7 +464,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task ToggleInvoiceArchival(string invoiceId, bool archived, string storeId = null)
|
public async Task ToggleInvoiceArchival(string invoiceId, bool archived, string storeId = null)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
var invoiceData = await context.FindAsync<InvoiceData>(invoiceId).ConfigureAwait(false);
|
||||||
if (invoiceData == null || invoiceData.Archived == archived ||
|
if (invoiceData == null || invoiceData.Archived == archived ||
|
||||||
@@ -477,14 +477,14 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
}
|
}
|
||||||
public async Task<InvoiceEntity> UpdateInvoiceMetadata(string invoiceId, string storeId, JObject metadata)
|
public async Task<InvoiceEntity> UpdateInvoiceMetadata(string invoiceId, string storeId, JObject metadata)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = await GetInvoiceRaw(invoiceId, context);
|
var invoiceData = await GetInvoiceRaw(invoiceId, context);
|
||||||
if (invoiceData == null || (storeId != null &&
|
if (invoiceData == null || (storeId != null &&
|
||||||
!invoiceData.StoreDataId.Equals(storeId,
|
!invoiceData.StoreDataId.Equals(storeId,
|
||||||
StringComparison.InvariantCultureIgnoreCase)))
|
StringComparison.InvariantCultureIgnoreCase)))
|
||||||
return null;
|
return null;
|
||||||
var blob = invoiceData.GetBlob(_Networks);
|
var blob = invoiceData.GetBlob(_btcPayNetworkProvider);
|
||||||
blob.Metadata = InvoiceMetadata.FromJObject(metadata);
|
blob.Metadata = InvoiceMetadata.FromJObject(metadata);
|
||||||
invoiceData.Blob = ToBytes(blob);
|
invoiceData.Blob = ToBytes(blob);
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
@@ -493,7 +493,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
}
|
}
|
||||||
public async Task<bool> MarkInvoiceStatus(string invoiceId, InvoiceStatus status)
|
public async Task<bool> MarkInvoiceStatus(string invoiceId, InvoiceStatus status)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var invoiceData = await GetInvoiceRaw(invoiceId, context);
|
var invoiceData = await GetInvoiceRaw(invoiceId, context);
|
||||||
if (invoiceData == null)
|
if (invoiceData == null)
|
||||||
@@ -538,7 +538,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task<InvoiceEntity> GetInvoice(string id, bool inludeAddressData = false)
|
public async Task<InvoiceEntity> GetInvoice(string id, bool inludeAddressData = false)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var res = await GetInvoiceRaw(id, context, inludeAddressData);
|
var res = await GetInvoiceRaw(id, context, inludeAddressData);
|
||||||
return res == null ? null : ToEntity(res);
|
return res == null ? null : ToEntity(res);
|
||||||
@@ -547,7 +547,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
public async Task<InvoiceEntity[]> GetInvoices(string[] invoiceIds)
|
public async Task<InvoiceEntity[]> GetInvoices(string[] invoiceIds)
|
||||||
{
|
{
|
||||||
var invoiceIdSet = invoiceIds.ToHashSet();
|
var invoiceIdSet = invoiceIds.ToHashSet();
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
IQueryable<Data.InvoiceData> query =
|
IQueryable<Data.InvoiceData> query =
|
||||||
context
|
context
|
||||||
@@ -578,12 +578,12 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
private InvoiceEntity ToEntity(Data.InvoiceData invoice)
|
private InvoiceEntity ToEntity(Data.InvoiceData invoice)
|
||||||
{
|
{
|
||||||
var entity = invoice.GetBlob(_Networks);
|
var entity = invoice.GetBlob(_btcPayNetworkProvider);
|
||||||
PaymentMethodDictionary paymentMethods = null;
|
PaymentMethodDictionary paymentMethods = null;
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
entity.Payments = invoice.Payments.Select(p =>
|
entity.Payments = invoice.Payments.Select(p =>
|
||||||
{
|
{
|
||||||
var paymentEntity = p.GetBlob(_Networks);
|
var paymentEntity = p.GetBlob(_btcPayNetworkProvider);
|
||||||
if (paymentEntity is null)
|
if (paymentEntity is null)
|
||||||
return null;
|
return null;
|
||||||
// PaymentEntity on version 0 does not have their own fee, because it was assumed that the payment method have fixed fee.
|
// PaymentEntity on version 0 does not have their own fee, because it was assumed that the payment method have fixed fee.
|
||||||
@@ -707,7 +707,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task<int> GetInvoicesTotal(InvoiceQuery queryObject)
|
public async Task<int> GetInvoicesTotal(InvoiceQuery queryObject)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var query = GetInvoiceQuery(context, queryObject);
|
var query = GetInvoiceQuery(context, queryObject);
|
||||||
return await query.CountAsync();
|
return await query.CountAsync();
|
||||||
@@ -716,7 +716,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject)
|
public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject)
|
||||||
{
|
{
|
||||||
using (var context = _ContextFactory.CreateContext())
|
using (var context = _applicationDbContextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
var query = GetInvoiceQuery(context, queryObject);
|
var query = GetInvoiceQuery(context, queryObject);
|
||||||
query = query.Include(o => o.Payments);
|
query = query.Include(o => o.Payments);
|
||||||
@@ -752,89 +752,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
internal static byte[] ToBytes<T>(T obj, BTCPayNetworkBase network = null)
|
||||||
/// Add a payment to an invoice
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="invoiceId"></param>
|
|
||||||
/// <param name="date"></param>
|
|
||||||
/// <param name="paymentData"></param>
|
|
||||||
/// <param name="cryptoCode"></param>
|
|
||||||
/// <param name="accounted"></param>
|
|
||||||
/// <returns>The PaymentEntity or null if already added</returns>
|
|
||||||
public async Task<PaymentEntity> AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, BTCPayNetworkBase network, bool accounted = false)
|
|
||||||
{
|
|
||||||
using (var context = _ContextFactory.CreateContext())
|
|
||||||
{
|
|
||||||
var invoice = context.Invoices.Find(invoiceId);
|
|
||||||
if (invoice == null)
|
|
||||||
return null;
|
|
||||||
InvoiceEntity invoiceEntity = invoice.GetBlob(_Networks);
|
|
||||||
PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()));
|
|
||||||
IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
|
||||||
PaymentEntity entity = new PaymentEntity
|
|
||||||
{
|
|
||||||
Version = 1,
|
|
||||||
#pragma warning disable CS0618
|
|
||||||
CryptoCode = network.CryptoCode,
|
|
||||||
#pragma warning restore CS0618
|
|
||||||
ReceivedTime = date.UtcDateTime,
|
|
||||||
Accounted = accounted,
|
|
||||||
NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
|
|
||||||
Network = network
|
|
||||||
};
|
|
||||||
entity.SetCryptoPaymentData(paymentData);
|
|
||||||
//TODO: abstract
|
|
||||||
if (paymentMethodDetails is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod bitcoinPaymentMethod &&
|
|
||||||
bitcoinPaymentMethod.NetworkFeeMode == NetworkFeeMode.MultiplePaymentsOnly &&
|
|
||||||
bitcoinPaymentMethod.NextNetworkFee == Money.Zero)
|
|
||||||
{
|
|
||||||
bitcoinPaymentMethod.NextNetworkFee = bitcoinPaymentMethod.NetworkFeeRate.GetFee(100); // assume price for 100 bytes
|
|
||||||
paymentMethod.SetPaymentMethodDetails(bitcoinPaymentMethod);
|
|
||||||
invoiceEntity.SetPaymentMethod(paymentMethod);
|
|
||||||
invoice.Blob = ToBytes(invoiceEntity, network);
|
|
||||||
}
|
|
||||||
PaymentData data = new PaymentData
|
|
||||||
{
|
|
||||||
Id = paymentData.GetPaymentId(),
|
|
||||||
Blob = ToBytes(entity, entity.Network),
|
|
||||||
InvoiceDataId = invoiceId,
|
|
||||||
Accounted = accounted
|
|
||||||
};
|
|
||||||
|
|
||||||
await context.Payments.AddAsync(data);
|
|
||||||
|
|
||||||
AddToTextSearch(context, invoice, paymentData.GetSearchTerms());
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (DbUpdateException) { return null; } // Already exists
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdatePayments(List<PaymentEntity> payments)
|
|
||||||
{
|
|
||||||
if (payments.Count == 0)
|
|
||||||
return;
|
|
||||||
using (var context = _ContextFactory.CreateContext())
|
|
||||||
{
|
|
||||||
foreach (var payment in payments)
|
|
||||||
{
|
|
||||||
var paymentData = payment.GetCryptoPaymentData();
|
|
||||||
var data = new PaymentData();
|
|
||||||
data.Id = paymentData.GetPaymentId();
|
|
||||||
data.Accounted = payment.Accounted;
|
|
||||||
data.Blob = ToBytes(payment, payment.Network);
|
|
||||||
context.Attach(data);
|
|
||||||
context.Entry(data).Property(o => o.Accounted).IsModified = true;
|
|
||||||
context.Entry(data).Property(o => o.Blob).IsModified = true;
|
|
||||||
}
|
|
||||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] ToBytes<T>(T obj, BTCPayNetworkBase network = null)
|
|
||||||
{
|
{
|
||||||
return ZipUtils.Zip(ToJsonString(obj, network));
|
return ZipUtils.Zip(ToJsonString(obj, network));
|
||||||
}
|
}
|
||||||
|
|||||||
129
BTCPayServer/Services/Invoices/PaymentService.cs
Normal file
129
BTCPayServer/Services/Invoices/PaymentService.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Events;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NBitcoin;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Services.Invoices
|
||||||
|
{
|
||||||
|
public class PaymentService
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContextFactory _applicationDbContextFactory;
|
||||||
|
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||||
|
private readonly EventAggregator _eventAggregator;
|
||||||
|
|
||||||
|
public PaymentService(EventAggregator eventAggregator, ApplicationDbContextFactory applicationDbContextFactory, BTCPayNetworkProvider btcPayNetworkProvider)
|
||||||
|
{
|
||||||
|
_applicationDbContextFactory = applicationDbContextFactory;
|
||||||
|
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Add a payment to an invoice
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="invoiceId"></param>
|
||||||
|
/// <param name="date"></param>
|
||||||
|
/// <param name="paymentData"></param>
|
||||||
|
/// <param name="cryptoCode"></param>
|
||||||
|
/// <param name="accounted"></param>
|
||||||
|
/// <returns>The PaymentEntity or null if already added</returns>
|
||||||
|
public async Task<PaymentEntity> AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, BTCPayNetworkBase network, bool accounted = false)
|
||||||
|
{
|
||||||
|
await using var context = _applicationDbContextFactory.CreateContext();
|
||||||
|
var invoice = await context.Invoices.FindAsync(invoiceId);
|
||||||
|
if (invoice == null)
|
||||||
|
return null;
|
||||||
|
InvoiceEntity invoiceEntity = invoice.GetBlob(_btcPayNetworkProvider);
|
||||||
|
PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()));
|
||||||
|
IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
||||||
|
PaymentEntity entity = new PaymentEntity
|
||||||
|
{
|
||||||
|
Version = 1,
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
CryptoCode = network.CryptoCode,
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
ReceivedTime = date.UtcDateTime,
|
||||||
|
Accounted = accounted,
|
||||||
|
NetworkFee = paymentMethodDetails.GetNextNetworkFee(),
|
||||||
|
Network = network
|
||||||
|
};
|
||||||
|
entity.SetCryptoPaymentData(paymentData);
|
||||||
|
//TODO: abstract
|
||||||
|
if (paymentMethodDetails is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod bitcoinPaymentMethod &&
|
||||||
|
bitcoinPaymentMethod.NetworkFeeMode == NetworkFeeMode.MultiplePaymentsOnly &&
|
||||||
|
bitcoinPaymentMethod.NextNetworkFee == Money.Zero)
|
||||||
|
{
|
||||||
|
bitcoinPaymentMethod.NextNetworkFee = bitcoinPaymentMethod.NetworkFeeRate.GetFee(100); // assume price for 100 bytes
|
||||||
|
paymentMethod.SetPaymentMethodDetails(bitcoinPaymentMethod);
|
||||||
|
invoiceEntity.SetPaymentMethod(paymentMethod);
|
||||||
|
invoice.Blob = InvoiceRepository.ToBytes(invoiceEntity, network);
|
||||||
|
}
|
||||||
|
PaymentData data = new PaymentData
|
||||||
|
{
|
||||||
|
Id = paymentData.GetPaymentId(),
|
||||||
|
Blob = InvoiceRepository.ToBytes(entity, entity.Network),
|
||||||
|
InvoiceDataId = invoiceId,
|
||||||
|
Accounted = accounted
|
||||||
|
};
|
||||||
|
|
||||||
|
await context.Payments.AddAsync(data);
|
||||||
|
|
||||||
|
InvoiceRepository.AddToTextSearch(context, invoice, paymentData.GetSearchTerms());
|
||||||
|
var alreadyExists = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (DbUpdateException) { alreadyExists = true; }
|
||||||
|
|
||||||
|
if (alreadyExists)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paymentData.PaymentConfirmed(entity, invoiceEntity.SpeedPolicy))
|
||||||
|
{
|
||||||
|
_eventAggregator.Publish(new InvoiceEvent(invoiceEntity, InvoiceEvent.PaymentSettled) { Payment = entity });
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdatePayments(List<PaymentEntity> payments)
|
||||||
|
{
|
||||||
|
if (payments.Count == 0)
|
||||||
|
return;
|
||||||
|
await using var context = _applicationDbContextFactory.CreateContext();
|
||||||
|
var paymentsDict = payments
|
||||||
|
.Select(entity => (entity, entity.GetCryptoPaymentData()))
|
||||||
|
.ToDictionary(tuple => tuple.Item2.GetPaymentId());
|
||||||
|
var paymentIds = paymentsDict.Keys.ToArray();
|
||||||
|
var dbPayments = await context.Payments
|
||||||
|
.Include(data => data.InvoiceData)
|
||||||
|
.Where(data => paymentIds.Contains(data.Id)).ToDictionaryAsync(data => data.Id);
|
||||||
|
var eventsToSend = new List<InvoiceEvent>();
|
||||||
|
foreach (KeyValuePair<string,(PaymentEntity entity, CryptoPaymentData)> payment in paymentsDict)
|
||||||
|
{
|
||||||
|
var dbPayment = dbPayments[payment.Key];
|
||||||
|
var invBlob = dbPayment.InvoiceData.GetBlob(_btcPayNetworkProvider);
|
||||||
|
var dbPaymentEntity = dbPayment.GetBlob(_btcPayNetworkProvider);
|
||||||
|
var wasConfirmed = dbPayment.GetBlob(_btcPayNetworkProvider).GetCryptoPaymentData()
|
||||||
|
.PaymentConfirmed(dbPaymentEntity, invBlob.SpeedPolicy);
|
||||||
|
if (!wasConfirmed && payment.Value.Item2.PaymentConfirmed(payment.Value.entity, invBlob.SpeedPolicy))
|
||||||
|
{
|
||||||
|
eventsToSend.Add(new InvoiceEvent(invBlob, InvoiceEvent.PaymentSettled) { Payment = payment.Value.entity });
|
||||||
|
}
|
||||||
|
|
||||||
|
dbPayment.Accounted = payment.Value.entity.Accounted;
|
||||||
|
dbPayment.Blob = InvoiceRepository.ToBytes(payment.Value.entity, payment.Value.entity.Network);
|
||||||
|
}
|
||||||
|
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
eventsToSend.ForEach(_eventAggregator.Publish);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
{
|
{
|
||||||
("A new invoice has been created", WebhookEventType.InvoiceCreated),
|
("A new invoice has been created", WebhookEventType.InvoiceCreated),
|
||||||
("A new payment has been received", WebhookEventType.InvoiceReceivedPayment),
|
("A new payment has been received", WebhookEventType.InvoiceReceivedPayment),
|
||||||
|
("A payment has been settled", WebhookEventType.InvoicePaymentSettled),
|
||||||
("An invoice is processing", WebhookEventType.InvoiceProcessing),
|
("An invoice is processing", WebhookEventType.InvoiceProcessing),
|
||||||
("An invoice has expired", WebhookEventType.InvoiceExpired),
|
("An invoice has expired", WebhookEventType.InvoiceExpired),
|
||||||
("An invoice has been settled", WebhookEventType.InvoiceSettled),
|
("An invoice has been settled", WebhookEventType.InvoiceSettled),
|
||||||
|
|||||||
@@ -41,7 +41,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [ "Webhooks" ],
|
"tags": [
|
||||||
|
"Webhooks"
|
||||||
|
],
|
||||||
"summary": "Create a new webhook",
|
"summary": "Create a new webhook",
|
||||||
"description": "Create a new webhook",
|
"description": "Create a new webhook",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
@@ -141,7 +143,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"put": {
|
"put": {
|
||||||
"tags": [ "Webhooks" ],
|
"tags": [
|
||||||
|
"Webhooks"
|
||||||
|
],
|
||||||
"summary": "Update a webhook",
|
"summary": "Update a webhook",
|
||||||
"description": "Update a webhook",
|
"description": "Update a webhook",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
@@ -187,7 +191,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"tags": [ "Webhooks" ],
|
"tags": [
|
||||||
|
"Webhooks"
|
||||||
|
],
|
||||||
"summary": "Delete a webhook",
|
"summary": "Delete a webhook",
|
||||||
"description": "Delete a webhook",
|
"description": "Delete a webhook",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
@@ -483,7 +489,11 @@
|
|||||||
"timestamp": {
|
"timestamp": {
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"description": "Timestamp of when the delivery got broadcasted",
|
"description": "Timestamp of when the delivery got broadcasted",
|
||||||
"allOf": [ {"$ref": "#/components/schemas/UnixTimestamp"}]
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/UnixTimestamp"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"httpCode": {
|
"httpCode": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
@@ -619,7 +629,11 @@
|
|||||||
},
|
},
|
||||||
"timestamp": {
|
"timestamp": {
|
||||||
"description": "The timestamp when this delivery has been created",
|
"description": "The timestamp when this delivery has been created",
|
||||||
"allOf": [ {"$ref": "#/components/schemas/UnixTimestamp"}]
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/UnixTimestamp"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -712,11 +726,28 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether this payment has been sent after expiration of the invoice",
|
"description": "Whether this payment has been sent after expiration of the invoice",
|
||||||
"nullable": false
|
"nullable": false
|
||||||
|
},
|
||||||
|
"paymentMethod": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "What payment method was used for this payment",
|
||||||
|
"nullable": false
|
||||||
|
},
|
||||||
|
"payment": {
|
||||||
|
"description": "Details about the payment",
|
||||||
|
"$ref": "#/components/schemas/InvoicePaymentMethodDataModel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"WebhookInvoicePaymentSettledEvent": {
|
||||||
|
"description": "Callback sent if the `type` is `InvoicePaymentSettled`",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/WebhookInvoiceReceivedPaymentEvent"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"WebhookInvoiceExpiredEvent": {
|
"WebhookInvoiceExpiredEvent": {
|
||||||
"description": "Callback sent if the `type` is `InvoiceExpired`",
|
"description": "Callback sent if the `type` is `InvoiceExpired`",
|
||||||
"allOf": [
|
"allOf": [
|
||||||
@@ -828,6 +859,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"InvoicePaymentSettled": {
|
||||||
|
"post": {
|
||||||
|
"summary": "InvoicePaymentSettled",
|
||||||
|
"description": "An payment relating to an invoice has settled",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "header",
|
||||||
|
"name": "BTCPay-Sig",
|
||||||
|
"required": true,
|
||||||
|
"description": "The HMAC of the body's byte with the secret's of the webhook. `sha256=HMAC256(UTF8(webhook's secret), body)`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "sha256=b438519edde5c8144a4f9bcec51a9d346eca6506887c2ceeae1c0092884a97b9"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Webhooks"
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/WebhookInvoicePaymentSettledEvent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"InvoicePaidInFull": {
|
"InvoicePaidInFull": {
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "InvoicePaidInFull",
|
"summary": "InvoicePaidInFull",
|
||||||
|
|||||||
Reference in New Issue
Block a user