Add extended notifications

This commit is contained in:
nicolas.dorier
2018-01-18 20:56:55 +09:00
parent 39d47e33f6
commit b0e9e10f7e
12 changed files with 157 additions and 137 deletions

View File

@@ -280,7 +280,8 @@ namespace BTCPayServer.Tests
OrderId = "orderId", OrderId = "orderId",
NotificationURL = callbackServer.GetUri().AbsoluteUri, NotificationURL = callbackServer.GetUri().AbsoluteUri,
ItemDesc = "Some description", ItemDesc = "Some description",
FullNotifications = true FullNotifications = true,
ExtendedNotifications = true
}); });
BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21); BitcoinUrlBuilder url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP21);
tester.ExplorerNode.SendToAddress(url.Address, url.Amount); tester.ExplorerNode.SendToAddress(url.Address, url.Amount);

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<Version>1.0.1.7</Version> <Version>1.0.1.8</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="Build\dockerfiles\**" /> <Compile Remove="Build\dockerfiles\**" />

View File

@@ -237,8 +237,7 @@ namespace BTCPayServer.Controllers
try try
{ {
leases.Add(_EventAggregator.Subscribe<Events.InvoiceDataChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId))); leases.Add(_EventAggregator.Subscribe<Events.InvoiceDataChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
leases.Add(_EventAggregator.Subscribe<Events.InvoicePaymentEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId))); leases.Add(_EventAggregator.Subscribe<Events.InvoiceEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
leases.Add(_EventAggregator.Subscribe<Events.InvoiceStatusChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId)));
while (true) while (true)
{ {
var message = await webSocket.ReceiveAsync(DummyBuffer, default(CancellationToken)); var message = await webSocket.ReceiveAsync(DummyBuffer, default(CancellationToken));
@@ -414,6 +413,7 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> InvalidatePaidInvoice(string invoiceId) public async Task<IActionResult> InvalidatePaidInvoice(string invoiceId)
{ {
await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId); await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId);
_EventAggregator.Publish(new InvoiceEvent(invoiceId, 1008, "invoice_markedInvalid"));
return RedirectToAction(nameof(ListInvoices)); return RedirectToAction(nameof(ListInvoices));
} }

View File

@@ -178,7 +178,7 @@ namespace BTCPayServer.Controllers
entity.SetCryptoData(cryptoDatas); entity.SetCryptoData(cryptoDatas);
entity.PosData = invoice.PosData; entity.PosData = invoice.PosData;
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider); entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
_EventAggregator.Publish(new Events.InvoiceCreatedEvent(entity.Id)); _EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created"));
var resp = entity.EntityToDTO(_NetworkProvider); var resp = entity.EntityToDTO(_NetworkProvider);
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" }; return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
} }

View File

@@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Events
{
public class InvoiceCreatedEvent
{
public InvoiceCreatedEvent(string id)
{
InvoiceId = id;
}
public string InvoiceId { get; set; }
public override string ToString()
{
return $"Invoice {InvoiceId} created";
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Invoices;
namespace BTCPayServer.Events
{
public class InvoiceEvent
{
public InvoiceEvent(InvoiceEntity invoice, int code, string name) : this(invoice.Id, code, name)
{
}
public InvoiceEvent(string invoiceId, int code, string name)
{
InvoiceId = invoiceId;
EventCode = code;
Name = name;
}
public string InvoiceId { get; set; }
public int EventCode { get; set; }
public string Name { get; set; }
public override string ToString()
{
return $"Invoice {InvoiceId} new event: {Name} ({EventCode})";
}
}
}

View File

@@ -7,19 +7,29 @@ namespace BTCPayServer.Events
{ {
public class InvoiceIPNEvent public class InvoiceIPNEvent
{ {
public InvoiceIPNEvent(string invoiceId) public InvoiceIPNEvent(string invoiceId, int? eventCode, string name)
{ {
InvoiceId = invoiceId; InvoiceId = invoiceId;
EventCode = eventCode;
Name = name;
} }
public int? EventCode { get; set; }
public string Name { get; set; }
public string InvoiceId { get; set; } public string InvoiceId { get; set; }
public string Error { get; set; } public string Error { get; set; }
public override string ToString() public override string ToString()
{ {
string ipnType = "IPN";
if(EventCode.HasValue)
{
ipnType = $"IPN ({EventCode.Value} {Name})";
}
if (Error == null) if (Error == null)
return $"IPN sent for invoice {InvoiceId}"; return $"{ipnType} sent for invoice {InvoiceId}";
return $"Error while sending IPN: {Error}"; return $"Error while sending {ipnType}: {Error}";
} }
} }
} }

View File

@@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Invoices;
namespace BTCPayServer.Events
{
public class InvoicePaymentEvent
{
public InvoicePaymentEvent(string invoiceId, string cryptoCode, string address)
{
InvoiceId = invoiceId;
Address = address;
CryptoCode = cryptoCode;
}
public string Address { get; set; }
public string CryptoCode { get; private set; }
public string InvoiceId { get; set; }
public override string ToString()
{
return $"{CryptoCode}: Invoice {InvoiceId} received a payment on {Address}";
}
}
}

View File

@@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Invoices;
namespace BTCPayServer.Events
{
public class InvoiceStatusChangedEvent
{
public InvoiceStatusChangedEvent()
{
}
public InvoiceStatusChangedEvent(InvoiceEntity invoice, string newState)
{
OldState = invoice.Status;
InvoiceId = invoice.Id;
NewState = newState;
}
public string InvoiceId { get; set; }
public string OldState { get; set; }
public string NewState { get; set; }
public override string ToString()
{
return $"Invoice {InvoiceId} changed status: {OldState} => {NewState}";
}
}
}

View File

@@ -37,6 +37,9 @@ namespace BTCPayServer.HostedServices
{ {
get; set; get; set;
} }
public int? EventCode { get; set; }
public string Message { get; set; }
} }
public ILogger Logger public ILogger Logger
@@ -63,32 +66,32 @@ namespace BTCPayServer.HostedServices
_NetworkProvider = networkProvider; _NetworkProvider = networkProvider;
} }
async Task Notify(InvoiceEntity invoice) async Task Notify(InvoiceEntity invoice, int? eventCode = null, string name = null)
{ {
CancellationTokenSource cts = new CancellationTokenSource(10000); CancellationTokenSource cts = new CancellationTokenSource(10000);
try try
{ {
if (string.IsNullOrEmpty(invoice.NotificationURL)) if (string.IsNullOrEmpty(invoice.NotificationURL))
return; return;
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id)); _EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id, eventCode, name));
await SendNotification(invoice, cts.Token); await SendNotification(invoice, eventCode, name, cts.Token);
return; return;
} }
catch(OperationCanceledException) when(cts.IsCancellationRequested) catch (OperationCanceledException) when (cts.IsCancellationRequested)
{ {
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id) _EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id, eventCode, name)
{ {
Error = "Timeout" Error = "Timeout"
}); });
} }
catch(Exception ex) // It fails, it is OK because we try with hangfire after catch (Exception ex) // It fails, it is OK because we try with hangfire after
{ {
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id) _EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(invoice.Id, eventCode, name)
{ {
Error = ex.Message Error = ex.Message
}); });
} }
var invoiceStr = NBitcoin.JsonConverters.Serializer.ToString(new ScheduledJob() { TryCount = 0, Invoice = invoice }); var invoiceStr = NBitcoin.JsonConverters.Serializer.ToString(new ScheduledJob() { TryCount = 0, Invoice = invoice, EventCode = eventCode, Message = name });
if (!string.IsNullOrEmpty(invoice.NotificationURL)) if (!string.IsNullOrEmpty(invoice.NotificationURL))
_JobClient.Schedule(() => NotifyHttp(invoiceStr), TimeSpan.Zero); _JobClient.Schedule(() => NotifyHttp(invoiceStr), TimeSpan.Zero);
} }
@@ -107,14 +110,18 @@ namespace BTCPayServer.HostedServices
CancellationTokenSource cts = new CancellationTokenSource(10000); CancellationTokenSource cts = new CancellationTokenSource(10000);
try try
{ {
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id)); HttpResponseMessage response = await SendNotification(job.Invoice, job.EventCode, job.Message, cts.Token);
HttpResponseMessage response = await SendNotification(job.Invoice, cts.Token);
reschedule = response.StatusCode != System.Net.HttpStatusCode.OK; reschedule = response.StatusCode != System.Net.HttpStatusCode.OK;
Logger.LogInformation("Job " + jobId + " returned " + response.StatusCode); Logger.LogInformation("Job " + jobId + " returned " + response.StatusCode);
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
{
Error = reschedule ? $"Unexpected return code: {(int)response.StatusCode}" : null
});
} }
catch (OperationCanceledException) when (cts.IsCancellationRequested) catch (OperationCanceledException) when (cts.IsCancellationRequested)
{ {
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id) _EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
{ {
Error = "Timeout" Error = "Timeout"
}); });
@@ -123,12 +130,25 @@ namespace BTCPayServer.HostedServices
} }
catch (Exception ex) // It fails, it is OK because we try with hangfire after catch (Exception ex) // It fails, it is OK because we try with hangfire after
{ {
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id) _EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
{ {
Error = ex.Message Error = ex.Message
}); });
reschedule = true; reschedule = true;
Logger.LogInformation("Job " + jobId + " threw exception " + ex.Message);
List<string> messages = new List<string>();
while(ex != null)
{
messages.Add(ex.Message);
ex = ex.InnerException;
}
string message = String.Join(',', messages.ToArray());
Logger.LogInformation("Job " + jobId + " threw exception " + message);
_EventAggregator.Publish<InvoiceIPNEvent>(new InvoiceIPNEvent(job.Invoice.Id, job.EventCode, job.Message)
{
Error = $"Unexpected error: {message}"
});
} }
finally { cts.Dispose(); _Executing.TryRemove(jobId, out jobId); } finally { cts.Dispose(); _Executing.TryRemove(jobId, out jobId); }
@@ -143,8 +163,23 @@ namespace BTCPayServer.HostedServices
} }
} }
public class InvoicePaymentNotificationEvent
{
[JsonProperty("code")]
public int Code { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
public class InvoicePaymentNotificationEventWrapper
{
[JsonProperty("event")]
public InvoicePaymentNotificationEvent Event { get; set; }
[JsonProperty("data")]
public InvoicePaymentNotification Data { get; set; }
}
Encoding UTF8 = new UTF8Encoding(false); Encoding UTF8 = new UTF8Encoding(false);
private async Task<HttpResponseMessage> SendNotification(InvoiceEntity invoice, CancellationToken cancellation) private async Task<HttpResponseMessage> SendNotification(InvoiceEntity invoice, int? eventCode, string name, CancellationToken cancellation)
{ {
var request = new HttpRequestMessage(); var request = new HttpRequestMessage();
request.Method = HttpMethod.Post; request.Method = HttpMethod.Post;
@@ -167,7 +202,7 @@ namespace BTCPayServer.HostedServices
// We keep backward compatibility with bitpay by passing BTC info to the notification // We keep backward compatibility with bitpay by passing BTC info to the notification
// we don't pass other info, as it is a bad idea to use IPN data for logic processing (can be faked) // we don't pass other info, as it is a bad idea to use IPN data for logic processing (can be faked)
var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "BTC"); var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "BTC");
if(btcCryptoInfo != null) if (btcCryptoInfo != null)
{ {
#pragma warning disable CS0618 #pragma warning disable CS0618
notification.Rate = (double)dto.Rate; notification.Rate = (double)dto.Rate;
@@ -177,8 +212,22 @@ namespace BTCPayServer.HostedServices
notification.BTCPrice = dto.BTCPrice; notification.BTCPrice = dto.BTCPrice;
#pragma warning restore CS0618 #pragma warning restore CS0618
} }
string notificationString = null;
if (eventCode.HasValue)
{
var wrapper = new InvoicePaymentNotificationEventWrapper();
wrapper.Data = notification;
wrapper.Event = new InvoicePaymentNotificationEvent() { Code = eventCode.Value, Name = name };
notificationString = JsonConvert.SerializeObject(wrapper);
}
else
{
notificationString = JsonConvert.SerializeObject(notification);
}
request.RequestUri = new Uri(invoice.NotificationURL, UriKind.Absolute); request.RequestUri = new Uri(invoice.NotificationURL, UriKind.Absolute);
request.Content = new StringContent(JsonConvert.SerializeObject(notification), UTF8, "application/json"); request.Content = new StringContent(notificationString, UTF8, "application/json");
var response = await _Client.SendAsync(request, cancellation); var response = await _Client.SendAsync(request, cancellation);
return response; return response;
} }
@@ -193,42 +242,41 @@ namespace BTCPayServer.HostedServices
CompositeDisposable leases = new CompositeDisposable(); CompositeDisposable leases = new CompositeDisposable();
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
leases.Add(_EventAggregator.Subscribe<InvoiceStatusChangedEvent>(async e => leases.Add(_EventAggregator.Subscribe<InvoiceEvent>(async e =>
{ {
var invoice = await _InvoiceRepository.GetInvoice(null, e.InvoiceId); var invoice = await _InvoiceRepository.GetInvoice(null, e.InvoiceId);
await SaveEvent(invoice.Id, e);
// we need to use the status in the event and not in the invoice. The invoice might now be in another status. // we need to use the status in the event and not in the invoice. The invoice might now be in another status.
if (invoice.FullNotifications) if (invoice.FullNotifications)
{ {
if (e.NewState == "expired" || if (e.Name == "invoice_expired" ||
e.NewState == "paid" || e.Name == "invoice_paidInFull" ||
e.NewState == "invalid" || e.Name == "invoice_failedToConfirm" ||
e.NewState == "complete" e.Name == "invoice_markedInvalid" ||
e.Name == "invoice_failedToConfirm" ||
e.Name == "invoice_completed"
) )
await Notify(invoice); await Notify(invoice);
} }
if(e.NewState == "confirmed") if (e.Name == "invoice_confirmed")
{ {
await Notify(invoice); await Notify(invoice);
} }
await SaveEvent(invoice.Id, e);
if (invoice.ExtendedNotifications)
{
await Notify(invoice, e.EventCode, e.Name);
}
})); }));
leases.Add(_EventAggregator.Subscribe<InvoiceCreatedEvent>(async e =>
{
await SaveEvent(e.InvoiceId, e);
}));
leases.Add(_EventAggregator.Subscribe<InvoiceDataChangedEvent>(async e => leases.Add(_EventAggregator.Subscribe<InvoiceDataChangedEvent>(async e =>
{ {
await SaveEvent(e.InvoiceId, e); await SaveEvent(e.InvoiceId, e);
})); }));
leases.Add(_EventAggregator.Subscribe<InvoicePaymentEvent>(async e =>
{
await SaveEvent(e.InvoiceId, e);
}));
leases.Add(_EventAggregator.Subscribe<InvoiceStopWatchedEvent>(async e => leases.Add(_EventAggregator.Subscribe<InvoiceStopWatchedEvent>(async e =>
{ {

View File

@@ -146,7 +146,7 @@ namespace BTCPayServer.HostedServices
context.MarkDirty(); context.MarkDirty();
await _InvoiceRepository.UnaffectAddress(invoice.Id); await _InvoiceRepository.UnaffectAddress(invoice.Id);
context.Events.Add(new InvoiceStatusChangedEvent(invoice, "expired")); context.Events.Add(new InvoiceEvent(invoice, 1004, "invoice_expired"));
invoice.Status = "expired"; invoice.Status = "expired";
} }
@@ -169,7 +169,7 @@ namespace BTCPayServer.HostedServices
invoice.Payments.Add(payment); invoice.Payments.Add(payment);
#pragma warning restore CS0618 #pragma warning restore CS0618
alreadyAccounted.Add(coin.Coin.Outpoint); alreadyAccounted.Add(coin.Coin.Outpoint);
context.Events.Add(new InvoicePaymentEvent(invoice.Id, coins.Wallet.Network.CryptoCode, coin.Coin.ScriptPubKey.GetDestinationAddress(coins.Wallet.Network.NBitcoinNetwork).ToString())); context.Events.Add(new InvoiceEvent(invoice, 1002, "invoice_receivedPayment"));
dirtyAddress = true; dirtyAddress = true;
} }
if (dirtyAddress) if (dirtyAddress)
@@ -188,7 +188,7 @@ namespace BTCPayServer.HostedServices
{ {
if (invoice.Status == "new") if (invoice.Status == "new")
{ {
context.Events.Add(new InvoiceStatusChangedEvent(invoice, "paid")); context.Events.Add(new InvoiceEvent(invoice, 1003, "invoice_paidInFull"));
invoice.Status = "paid"; invoice.Status = "paid";
invoice.ExceptionStatus = null; invoice.ExceptionStatus = null;
await _InvoiceRepository.UnaffectAddress(invoice.Id); await _InvoiceRepository.UnaffectAddress(invoice.Id);
@@ -197,6 +197,7 @@ namespace BTCPayServer.HostedServices
else if (invoice.Status == "expired") else if (invoice.Status == "expired")
{ {
invoice.ExceptionStatus = "paidLate"; invoice.ExceptionStatus = "paidLate";
context.Events.Add(new InvoiceEvent(invoice, 1009, "invoice_paidAfterExpiration"));
context.MarkDirty(); context.MarkDirty();
} }
} }
@@ -246,14 +247,14 @@ namespace BTCPayServer.HostedServices
(totalConfirmed < accounting.TotalDue)) (totalConfirmed < accounting.TotalDue))
{ {
await _InvoiceRepository.UnaffectAddress(invoice.Id); await _InvoiceRepository.UnaffectAddress(invoice.Id);
context.Events.Add(new InvoiceStatusChangedEvent(invoice, "invalid")); context.Events.Add(new InvoiceEvent(invoice, 1013, "invoice_failedToConfirm"));
invoice.Status = "invalid"; invoice.Status = "invalid";
context.MarkDirty(); context.MarkDirty();
} }
else if (totalConfirmed >= accounting.TotalDue) else if (totalConfirmed >= accounting.TotalDue)
{ {
await _InvoiceRepository.UnaffectAddress(invoice.Id); await _InvoiceRepository.UnaffectAddress(invoice.Id);
context.Events.Add(new InvoiceStatusChangedEvent(invoice, "confirmed")); context.Events.Add(new InvoiceEvent(invoice, 1005, "invoice_confirmed"));
invoice.Status = "confirmed"; invoice.Status = "confirmed";
context.MarkDirty(); context.MarkDirty();
} }
@@ -266,7 +267,7 @@ namespace BTCPayServer.HostedServices
var totalConfirmed = transactions.Select(t => t.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum(); var totalConfirmed = transactions.Select(t => t.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
if (totalConfirmed >= accounting.TotalDue) if (totalConfirmed >= accounting.TotalDue)
{ {
context.Events.Add(new InvoiceStatusChangedEvent(invoice, "complete")); context.Events.Add(new InvoiceEvent(invoice, 1006, "invoice_completed"));
invoice.Status = "complete"; invoice.Status = "complete";
context.MarkDirty(); context.MarkDirty();
} }
@@ -442,7 +443,13 @@ namespace BTCPayServer.HostedServices
leases.Add(_EventAggregator.Subscribe<Events.NewBlockEvent>(async b => { await NotifyBlock(); })); leases.Add(_EventAggregator.Subscribe<Events.NewBlockEvent>(async b => { await NotifyBlock(); }));
leases.Add(_EventAggregator.Subscribe<Events.TxOutReceivedEvent>(async b => { await NotifyReceived(b.ScriptPubKey, b.Network); })); leases.Add(_EventAggregator.Subscribe<Events.TxOutReceivedEvent>(async b => { await NotifyReceived(b.ScriptPubKey, b.Network); }));
leases.Add(_EventAggregator.Subscribe<Events.InvoiceCreatedEvent>(async b => { await Watch(b.InvoiceId); })); leases.Add(_EventAggregator.Subscribe<Events.InvoiceEvent>(async b =>
{
if(b.Name == "invoice_created")
{
await Watch(b.InvoiceId);
}
}));
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@@ -87,19 +87,22 @@ namespace BTCPayServer.HostedServices
}, null, 0, (int)PollInterval.TotalMilliseconds); }, null, 0, (int)PollInterval.TotalMilliseconds);
leases.Add(_ListenPoller); leases.Add(_ListenPoller);
leases.Add(_Aggregator.Subscribe<Events.InvoiceCreatedEvent>(async inv => leases.Add(_Aggregator.Subscribe<Events.InvoiceEvent>(async inv =>
{ {
var invoice = await _InvoiceRepository.GetInvoice(null, inv.InvoiceId); if (inv.Name == "invoice_created")
List<Task> listeningDerivations = new List<Task>();
foreach (var notificationSessions in _Sessions)
{ {
var derivationStrategy = GetStrategy(notificationSessions.Key, invoice); var invoice = await _InvoiceRepository.GetInvoice(null, inv.InvoiceId);
if (derivationStrategy != null) List<Task> listeningDerivations = new List<Task>();
foreach (var notificationSessions in _Sessions)
{ {
listeningDerivations.Add(notificationSessions.Value.ListenDerivationSchemesAsync(new[] { derivationStrategy }, _Cts.Token)); var derivationStrategy = GetStrategy(notificationSessions.Key, invoice);
if (derivationStrategy != null)
{
listeningDerivations.Add(notificationSessions.Value.ListenDerivationSchemesAsync(new[] { derivationStrategy }, _Cts.Token));
}
} }
await Task.WhenAll(listeningDerivations.ToArray()).ConfigureAwait(false);
} }
await Task.WhenAll(listeningDerivations.ToArray()).ConfigureAwait(false);
})); }));
return Task.CompletedTask; return Task.CompletedTask;
} }