Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
cde593a935 | |||
f0755260a6 | |||
582e1eb4f8 | |||
aaadda3e0f | |||
9d7f5b5b6e | |||
99040597dc | |||
d9794216dd | |||
84bb6056d3 | |||
dfed2daa8e |
@ -230,7 +230,7 @@ namespace BTCPayServer.Tests
|
||||
var match = new TransactionMatch();
|
||||
match.Outputs.Add(new KeyPathInformation() { ScriptPubKey = address.ScriptPubKey });
|
||||
var content = new StringContent(new NBXplorer.Serializer(Network).ToString(match), new UTF8Encoding(false), "application/json");
|
||||
var uri = controller.GetCallbackUriAsync(req).GetAwaiter().GetResult();
|
||||
var uri = controller.GetCallbackUriAsync().GetAwaiter().GetResult();
|
||||
|
||||
HttpRequestMessage message = new HttpRequestMessage();
|
||||
message.Method = HttpMethod.Post;
|
||||
@ -242,7 +242,7 @@ namespace BTCPayServer.Tests
|
||||
else
|
||||
{
|
||||
|
||||
var uri = controller.GetCallbackBlockUriAsync(req).GetAwaiter().GetResult();
|
||||
var uri = controller.GetCallbackBlockUriAsync().GetAwaiter().GetResult();
|
||||
HttpRequestMessage message = new HttpRequestMessage();
|
||||
message.Method = HttpMethod.Post;
|
||||
message.RequestUri = uri;
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<Version>1.0.0.41</Version>
|
||||
<Version>1.0.0.44</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Build\dockerfiles\**" />
|
||||
|
19
BTCPayServer/CompositeDisposable.cs
Normal file
19
BTCPayServer/CompositeDisposable.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class CompositeDisposable : IDisposable
|
||||
{
|
||||
List<IDisposable> _Disposables = new List<IDisposable>();
|
||||
public void Add(IDisposable disposable) { _Disposables.Add(disposable); }
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var d in _Disposables)
|
||||
d.Dispose();
|
||||
_Disposables.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Events;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -30,21 +34,23 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
SettingsRepository _Settings;
|
||||
Network _Network;
|
||||
InvoiceWatcher _Watcher;
|
||||
ExplorerClient _Explorer;
|
||||
BTCPayServerOptions _Options;
|
||||
|
||||
EventAggregator _EventAggregator;
|
||||
IServer _Server;
|
||||
public CallbackController(SettingsRepository repo,
|
||||
ExplorerClient explorer,
|
||||
InvoiceWatcherAccessor watcher,
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayServerOptions options,
|
||||
IServer server,
|
||||
Network network)
|
||||
{
|
||||
_Settings = repo;
|
||||
_Network = network;
|
||||
_Watcher = watcher.Instance;
|
||||
_Explorer = explorer;
|
||||
_Options = options;
|
||||
_EventAggregator = eventAggregator;
|
||||
_Server = server;
|
||||
}
|
||||
|
||||
[Route("callbacks/transactions")]
|
||||
@ -52,7 +58,6 @@ namespace BTCPayServer.Controllers
|
||||
public async Task NewTransaction(string token)
|
||||
{
|
||||
await AssertToken(token);
|
||||
Logs.PayServer.LogInformation("New transaction callback");
|
||||
//We don't want to register all the json converter at MVC level, so we parse here
|
||||
var serializer = new NBXplorer.Serializer(_Network);
|
||||
var content = await new StreamReader(Request.Body, new UTF8Encoding(false), false, 1024, true).ReadToEndAsync();
|
||||
@ -60,7 +65,10 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
foreach (var output in match.Outputs)
|
||||
{
|
||||
await _Watcher.NotifyReceived(output.ScriptPubKey);
|
||||
var evt = new TxOutReceivedEvent();
|
||||
evt.ScriptPubKey = output.ScriptPubKey;
|
||||
evt.Address = output.ScriptPubKey.GetDestinationAddress(_Network);
|
||||
_EventAggregator.Publish(evt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,8 +77,7 @@ namespace BTCPayServer.Controllers
|
||||
public async Task NewBlock(string token)
|
||||
{
|
||||
await AssertToken(token);
|
||||
Logs.PayServer.LogInformation("New block callback");
|
||||
await _Watcher.NotifyBlock();
|
||||
_EventAggregator.Publish(new NewBlockEvent());
|
||||
}
|
||||
|
||||
private async Task AssertToken(string token)
|
||||
@ -80,15 +87,15 @@ namespace BTCPayServer.Controllers
|
||||
throw new BTCPayServer.BitpayHttpException(400, "invalid-callback-token");
|
||||
}
|
||||
|
||||
public async Task<Uri> GetCallbackUriAsync(HttpRequest request)
|
||||
public async Task<Uri> GetCallbackUriAsync()
|
||||
{
|
||||
string token = await GetToken();
|
||||
return BuildCallbackUri(request, "callbacks/transactions?token=" + token);
|
||||
return BuildCallbackUri("callbacks/transactions?token=" + token);
|
||||
}
|
||||
|
||||
public async Task RegisterCallbackUriAsync(DerivationStrategyBase derivationScheme, HttpRequest request)
|
||||
public async Task RegisterCallbackUriAsync(DerivationStrategyBase derivationScheme)
|
||||
{
|
||||
var uri = await GetCallbackUriAsync(request);
|
||||
var uri = await GetCallbackUriAsync();
|
||||
await _Explorer.SubscribeToWalletAsync(uri, derivationScheme);
|
||||
}
|
||||
|
||||
@ -104,19 +111,29 @@ namespace BTCPayServer.Controllers
|
||||
return token;
|
||||
}
|
||||
|
||||
public async Task<Uri> GetCallbackBlockUriAsync(HttpRequest request)
|
||||
public async Task<Uri> GetCallbackBlockUriAsync()
|
||||
{
|
||||
string token = await GetToken();
|
||||
return BuildCallbackUri(request, "callbacks/blocks?token=" + token);
|
||||
return BuildCallbackUri("callbacks/blocks?token=" + token);
|
||||
}
|
||||
|
||||
private Uri BuildCallbackUri(HttpRequest request, string callbackPath)
|
||||
private Uri BuildCallbackUri(string callbackPath)
|
||||
{
|
||||
string baseUrl = _Options.InternalUrl == null ? request.GetAbsoluteRoot() : _Options.InternalUrl.AbsolutePath;
|
||||
var address = _Server.Features.Get<IServerAddressesFeature>().Addresses
|
||||
.Select(c => new Uri(TransformToRoutable(c)))
|
||||
.First();
|
||||
var baseUrl = _Options.InternalUrl == null ? address.AbsoluteUri : _Options.InternalUrl.AbsoluteUri;
|
||||
baseUrl = baseUrl.WithTrailingSlash();
|
||||
return new Uri(baseUrl + callbackPath);
|
||||
}
|
||||
|
||||
private string TransformToRoutable(string host)
|
||||
{
|
||||
if (host.StartsWith("http://0.0.0.0"))
|
||||
host = host.Replace("http://0.0.0.0", "http://127.0.0.1");
|
||||
return host;
|
||||
}
|
||||
|
||||
public async Task<Uri> RegisterCallbackBlockUriAsync(Uri uri)
|
||||
{
|
||||
await _Explorer.SubscribeToBlocksAsync(uri);
|
||||
|
@ -16,12 +16,14 @@ using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using BTCPayServer.Events;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class InvoiceController
|
||||
{
|
||||
|
||||
[HttpPost]
|
||||
[Route("invoices/{invoiceId}")]
|
||||
public IActionResult Invoice(string invoiceId, string command)
|
||||
@ -169,6 +171,73 @@ namespace BTCPayServer.Controllers
|
||||
return Json(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("i/{invoiceId}/status/ws")]
|
||||
public async Task<IActionResult> GetStatusWebSocket(string invoiceId)
|
||||
{
|
||||
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||
return NotFound();
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||
if (invoice == null || invoice.Status == "complete" || invoice.Status == "invalid" || invoice.Status == "expired")
|
||||
return NotFound();
|
||||
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
try
|
||||
{
|
||||
_EventAggregator.Subscribe<Events.InvoiceDataChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId));
|
||||
_EventAggregator.Subscribe<Events.InvoicePaymentEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId));
|
||||
_EventAggregator.Subscribe<Events.InvoiceStatusChangedEvent>(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId));
|
||||
while (true)
|
||||
{
|
||||
var message = await webSocket.ReceiveAsync(DummyBuffer, default(CancellationToken));
|
||||
if (message.MessageType == WebSocketMessageType.Close)
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
leases.Dispose();
|
||||
await CloseSocket(webSocket);
|
||||
}
|
||||
return new NoResponse();
|
||||
}
|
||||
|
||||
class NoResponse : IActionResult
|
||||
{
|
||||
public Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
ArraySegment<Byte> DummyBuffer = new ArraySegment<Byte>(new Byte[1]);
|
||||
private async Task NotifySocket(WebSocket webSocket, string invoiceId, string expectedId)
|
||||
{
|
||||
if (invoiceId != expectedId || webSocket.State != WebSocketState.Open)
|
||||
return;
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(5000);
|
||||
try
|
||||
{
|
||||
await webSocket.SendAsync(DummyBuffer, WebSocketMessageType.Binary, true, cts.Token);
|
||||
}
|
||||
catch { await CloseSocket(webSocket); }
|
||||
}
|
||||
|
||||
private static async Task CloseSocket(WebSocket webSocket)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (webSocket.State == WebSocketState.Open)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(5000);
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cts.Token);
|
||||
}
|
||||
}
|
||||
finally { webSocket.Dispose(); }
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("i/{invoiceId}/UpdateCustomer")]
|
||||
public async Task<IActionResult> UpdateCustomer(string invoiceId, [FromBody]UpdateCustomerModel data)
|
||||
|
@ -53,7 +53,7 @@ namespace BTCPayServer.Controllers
|
||||
IFeeProvider _FeeProvider;
|
||||
private CurrencyNameTable _CurrencyNameTable;
|
||||
ExplorerClient _Explorer;
|
||||
|
||||
EventAggregator _EventAggregator;
|
||||
public InvoiceController(
|
||||
Network network,
|
||||
InvoiceRepository invoiceRepository,
|
||||
@ -62,6 +62,7 @@ namespace BTCPayServer.Controllers
|
||||
BTCPayWallet wallet,
|
||||
IRateProvider rateProvider,
|
||||
StoreRepository storeRepository,
|
||||
EventAggregator eventAggregator,
|
||||
InvoiceWatcherAccessor watcher,
|
||||
ExplorerClient explorerClient,
|
||||
IFeeProvider feeProvider)
|
||||
@ -76,6 +77,7 @@ namespace BTCPayServer.Controllers
|
||||
_Watcher = (watcher ?? throw new ArgumentNullException(nameof(watcher))).Instance;
|
||||
_UserManager = userManager;
|
||||
_FeeProvider = feeProvider ?? throw new ArgumentNullException(nameof(feeProvider));
|
||||
_EventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15)
|
||||
|
@ -198,7 +198,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var strategy = ParseDerivationStrategy(model.DerivationScheme, model.DerivationSchemeFormat);
|
||||
await _Wallet.TrackAsync(strategy);
|
||||
await _CallbackController.RegisterCallbackUriAsync(strategy, Request);
|
||||
await _CallbackController.RegisterCallbackUriAsync(strategy);
|
||||
model.DerivationScheme = strategy.ToString();
|
||||
}
|
||||
store.DerivationStrategy = model.DerivationScheme;
|
||||
}
|
||||
|
130
BTCPayServer/EventAggregator.cs
Normal file
130
BTCPayServer/EventAggregator.cs
Normal file
@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Logging;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public interface IEventAggregatorSubscription : IDisposable
|
||||
{
|
||||
void Unsubscribe();
|
||||
void Resubscribe();
|
||||
}
|
||||
public class EventAggregator : IDisposable
|
||||
{
|
||||
class Subscription : IEventAggregatorSubscription
|
||||
{
|
||||
private EventAggregator aggregator;
|
||||
Type t;
|
||||
Action<object> act;
|
||||
public Subscription(EventAggregator aggregator, Type t)
|
||||
{
|
||||
this.aggregator = aggregator;
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
public Action<Object> Act { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (this.aggregator._Subscriptions)
|
||||
{
|
||||
if (this.aggregator._Subscriptions.TryGetValue(t, out Dictionary<Subscription, Action<object>> actions))
|
||||
{
|
||||
if (actions.Remove(this))
|
||||
{
|
||||
if (actions.Count == 0)
|
||||
this.aggregator._Subscriptions.Remove(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Resubscribe()
|
||||
{
|
||||
aggregator.Subscribe(t, this);
|
||||
}
|
||||
|
||||
public void Unsubscribe()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Publish<T>(T evt) where T : class
|
||||
{
|
||||
if (evt == null)
|
||||
throw new ArgumentNullException(nameof(evt));
|
||||
List<Action<object>> actionList = new List<Action<object>>();
|
||||
lock (_Subscriptions)
|
||||
{
|
||||
if (_Subscriptions.TryGetValue(typeof(T), out Dictionary<Subscription, Action<object>> actions))
|
||||
{
|
||||
actionList = actions.Values.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
Logs.Events.LogInformation($"New event: {evt.ToString()}");
|
||||
foreach (var sub in actionList)
|
||||
{
|
||||
try
|
||||
{
|
||||
sub(evt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.Events.LogError(ex, $"Error while calling event handler");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEventAggregatorSubscription Subscribe<T>(Action<IEventAggregatorSubscription, T> subscription)
|
||||
{
|
||||
var eventType = typeof(T);
|
||||
var s = new Subscription(this, eventType);
|
||||
s.Act = (o) => subscription(s, (T)o);
|
||||
return Subscribe(eventType, s);
|
||||
}
|
||||
|
||||
private IEventAggregatorSubscription Subscribe(Type eventType, Subscription subscription)
|
||||
{
|
||||
lock (_Subscriptions)
|
||||
{
|
||||
if (!_Subscriptions.TryGetValue(eventType, out Dictionary<Subscription, Action<object>> actions))
|
||||
{
|
||||
actions = new Dictionary<Subscription, Action<object>>();
|
||||
_Subscriptions.Add(eventType, actions);
|
||||
}
|
||||
actions.Add(subscription, subscription.Act);
|
||||
}
|
||||
return subscription;
|
||||
}
|
||||
|
||||
Dictionary<Type, Dictionary<Subscription, Action<object>>> _Subscriptions = new Dictionary<Type, Dictionary<Subscription, Action<object>>>();
|
||||
|
||||
public IEventAggregatorSubscription Subscribe<T, TReturn>(Func<T, TReturn> subscription)
|
||||
{
|
||||
return Subscribe(new Action<T>((t) => subscription(t)));
|
||||
}
|
||||
|
||||
public IEventAggregatorSubscription Subscribe<T, TReturn>(Func<IEventAggregatorSubscription, T, TReturn> subscription)
|
||||
{
|
||||
return Subscribe(new Action<IEventAggregatorSubscription, T>((sub, t) => subscription(sub, t)));
|
||||
}
|
||||
|
||||
public IEventAggregatorSubscription Subscribe<T>(Action<T> subscription)
|
||||
{
|
||||
return Subscribe(new Action<IEventAggregatorSubscription, T>((sub, t) => subscription(t)));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_Subscriptions)
|
||||
{
|
||||
_Subscriptions.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
BTCPayServer/Events/InvoiceDataChangedEvent.cs
Normal file
17
BTCPayServer/Events/InvoiceDataChangedEvent.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class InvoiceDataChangedEvent
|
||||
{
|
||||
public string InvoiceId { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Invoice {InvoiceId} data changed";
|
||||
}
|
||||
}
|
||||
}
|
24
BTCPayServer/Events/InvoicePaymentEvent.cs
Normal file
24
BTCPayServer/Events/InvoicePaymentEvent.cs
Normal file
@ -0,0 +1,24 @@
|
||||
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)
|
||||
{
|
||||
InvoiceId = invoiceId;
|
||||
}
|
||||
|
||||
public string InvoiceId { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Invoice {InvoiceId} received a payment";
|
||||
}
|
||||
}
|
||||
}
|
30
BTCPayServer/Events/InvoiceStatusChangedEvent.cs
Normal file
30
BTCPayServer/Events/InvoiceStatusChangedEvent.cs
Normal file
@ -0,0 +1,30 @@
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
24
BTCPayServer/Events/NBXplorerStateChangedEvent.cs
Normal file
24
BTCPayServer/Events/NBXplorerStateChangedEvent.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class NBXplorerStateChangedEvent
|
||||
{
|
||||
public NBXplorerStateChangedEvent(NBXplorerState old, NBXplorerState newState)
|
||||
{
|
||||
NewState = newState;
|
||||
OldState = old;
|
||||
}
|
||||
|
||||
public NBXplorerState NewState { get; set; }
|
||||
public NBXplorerState OldState { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"NBXplorer: {OldState} => {NewState}";
|
||||
}
|
||||
}
|
||||
}
|
15
BTCPayServer/Events/NewBlockEvent.cs
Normal file
15
BTCPayServer/Events/NewBlockEvent.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class NewBlockEvent
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "New block";
|
||||
}
|
||||
}
|
||||
}
|
20
BTCPayServer/Events/TxOutReceivedEvent.cs
Normal file
20
BTCPayServer/Events/TxOutReceivedEvent.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class TxOutReceivedEvent
|
||||
{
|
||||
public Script ScriptPubKey { get; set; }
|
||||
public BitcoinAddress Address { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
String address = Address?.ToString() ?? ScriptPubKey.ToHex();
|
||||
return $"{address} received a transaction";
|
||||
}
|
||||
}
|
||||
}
|
@ -106,6 +106,7 @@ namespace BTCPayServer.Hosting
|
||||
});
|
||||
services.AddSingleton<BTCPayServerEnvironment>();
|
||||
services.TryAddSingleton<TokenRepository>();
|
||||
services.TryAddSingleton<EventAggregator>();
|
||||
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network);
|
||||
services.TryAddSingleton<ApplicationDbContextFactory>(o =>
|
||||
{
|
||||
@ -158,11 +159,11 @@ namespace BTCPayServer.Hosting
|
||||
return new CachedRateProvider(new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay }), o.GetRequiredService<IMemoryCache>()) { CacheSpan = TimeSpan.FromMinutes(1.0) };
|
||||
});
|
||||
|
||||
services.TryAddSingleton<InvoiceNotificationManager>();
|
||||
services.AddSingleton<IHostedService, InvoiceNotificationManager>();
|
||||
|
||||
services.TryAddSingleton<InvoiceWatcherAccessor>();
|
||||
services.AddSingleton<IHostedService, InvoiceWatcher>();
|
||||
|
||||
services.TryAddSingleton<Initializer>();
|
||||
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
|
||||
services.AddTransient<AccessTokenController>();
|
||||
@ -197,6 +198,11 @@ namespace BTCPayServer.Hosting
|
||||
scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
var initialize = app.ApplicationServices.GetService<Initializer>();
|
||||
initialize.Init();
|
||||
app.UseMiddleware<BTCPayMiddleware>();
|
||||
return app;
|
||||
}
|
||||
|
@ -29,31 +29,21 @@ namespace BTCPayServer.Hosting
|
||||
{
|
||||
TokenRepository _TokenRepository;
|
||||
RequestDelegate _Next;
|
||||
CallbackController _CallbackController;
|
||||
BTCPayServerOptions _Options;
|
||||
private NBXplorerWaiterAccessor _NbxplorerAwaiter;
|
||||
|
||||
public BTCPayMiddleware(RequestDelegate next,
|
||||
TokenRepository tokenRepo,
|
||||
BTCPayServerOptions options,
|
||||
NBXplorerWaiterAccessor nbxplorerAwaiter,
|
||||
CallbackController callbackController)
|
||||
BTCPayServerOptions options)
|
||||
{
|
||||
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
||||
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||
_CallbackController = callbackController;
|
||||
_Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_NbxplorerAwaiter = (nbxplorerAwaiter ?? throw new ArgumentNullException(nameof(nbxplorerAwaiter)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool _Registered;
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
RewriteHostIfNeeded(httpContext);
|
||||
await EnsureBlockCallbackRegistered(httpContext);
|
||||
|
||||
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
||||
var sig = values.FirstOrDefault();
|
||||
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
||||
@ -154,28 +144,6 @@ namespace BTCPayServer.Hosting
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnsureBlockCallbackRegistered(HttpContext httpContext)
|
||||
{
|
||||
if (!_Registered)
|
||||
{
|
||||
var callback = await _CallbackController.GetCallbackBlockUriAsync(httpContext.Request);
|
||||
var unused = _NbxplorerAwaiter.Instance.WhenReady(async c =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await _CallbackController.RegisterCallbackBlockUriAsync(callback);
|
||||
Logs.PayServer.LogInformation($"Registering block callback to " + callback);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogError(ex, "Could not register block callback");
|
||||
}
|
||||
return true;
|
||||
});
|
||||
_Registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandleBitpayHttpException(HttpContext httpContext, BitpayHttpException ex)
|
||||
{
|
||||
httpContext.Response.StatusCode = ex.StatusCode;
|
||||
|
@ -144,6 +144,7 @@ namespace BTCPayServer.Hosting
|
||||
app.UseAuthentication();
|
||||
app.UseHangfireServer();
|
||||
app.UseHangfireDashboard("/hangfire", new DashboardOptions() { Authorization = new[] { new NeedRole(Roles.ServerAdmin) } });
|
||||
app.UseWebSockets();
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
|
45
BTCPayServer/Initializer.cs
Normal file
45
BTCPayServer/Initializer.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Events;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class Initializer
|
||||
{
|
||||
EventAggregator _Aggregator;
|
||||
CallbackController _CallbackController;
|
||||
public Initializer(EventAggregator aggregator,
|
||||
CallbackController callbackController
|
||||
)
|
||||
{
|
||||
_Aggregator = aggregator;
|
||||
_CallbackController = callbackController;
|
||||
}
|
||||
public void Init()
|
||||
{
|
||||
_Aggregator.Subscribe<NBXplorerStateChangedEvent>(async (s, evt) =>
|
||||
{
|
||||
if (evt.NewState == NBXplorerState.Ready)
|
||||
{
|
||||
s.Unsubscribe();
|
||||
try
|
||||
{
|
||||
var callback = await _CallbackController.GetCallbackBlockUriAsync();
|
||||
await _CallbackController.RegisterCallbackBlockUriAsync(callback);
|
||||
Logs.PayServer.LogInformation($"Registering block callback to " + callback);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.PayServer.LogError(ex, "Could not register block callback");
|
||||
s.Resubscribe();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ namespace BTCPayServer.Logging
|
||||
{
|
||||
Configuration = factory.CreateLogger("Configuration");
|
||||
PayServer = factory.CreateLogger("PayServer");
|
||||
Events = factory.CreateLogger("PayServer");
|
||||
}
|
||||
public static ILogger Configuration
|
||||
{
|
||||
@ -26,6 +27,12 @@ namespace BTCPayServer.Logging
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public static ILogger Events
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public const int ColumnLength = 16;
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ using Microsoft.Extensions.Hosting;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Models;
|
||||
using System.Collections.Concurrent;
|
||||
using BTCPayServer.Events;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
@ -22,14 +23,17 @@ namespace BTCPayServer
|
||||
Synching,
|
||||
Ready
|
||||
}
|
||||
|
||||
public class NBXplorerWaiter : IHostedService
|
||||
{
|
||||
public NBXplorerWaiter(ExplorerClient client, NBXplorerWaiterAccessor accessor)
|
||||
public NBXplorerWaiter(ExplorerClient client, EventAggregator aggregator, NBXplorerWaiterAccessor accessor)
|
||||
{
|
||||
_Client = client;
|
||||
_Aggregator = aggregator;
|
||||
accessor.Instance = this;
|
||||
}
|
||||
|
||||
EventAggregator _Aggregator;
|
||||
ExplorerClient _Client;
|
||||
Timer _Timer;
|
||||
ManualResetEventSlim _Idle = new ManualResetEventSlim(true);
|
||||
@ -64,15 +68,6 @@ namespace BTCPayServer
|
||||
{
|
||||
|
||||
}
|
||||
List<Task> tasks = new List<Task>();
|
||||
if (State == NBXplorerState.Ready)
|
||||
{
|
||||
while (_WhenReady.TryDequeue(out Func<ExplorerClient, Task> act))
|
||||
{
|
||||
tasks.Add(act(_Client));
|
||||
}
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private async Task<bool> StepAsync()
|
||||
@ -119,36 +114,32 @@ namespace BTCPayServer
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
LastStatus = status;
|
||||
if (oldState != State)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"NBXplorerWaiter status changed: {oldState} => {State}");
|
||||
if (State == NBXplorerState.Synching)
|
||||
{
|
||||
SetInterval(TimeSpan.FromSeconds(10));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetInterval(TimeSpan.FromMinutes(1));
|
||||
}
|
||||
_Aggregator.Publish(new NBXplorerStateChangedEvent(oldState, State));
|
||||
}
|
||||
return oldState != State;
|
||||
}
|
||||
|
||||
public Task<T> WhenReady<T>(Func<ExplorerClient, Task<T>> act)
|
||||
private void SetInterval(TimeSpan interval)
|
||||
{
|
||||
if (State == NBXplorerState.Ready)
|
||||
return act(_Client);
|
||||
TaskCompletionSource<T> completion = new TaskCompletionSource<T>();
|
||||
_WhenReady.Enqueue(async client =>
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await act(client);
|
||||
completion.SetResult(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
completion.SetException(ex);
|
||||
}
|
||||
});
|
||||
return completion.Task;
|
||||
_Timer.Change(0, (int)interval.TotalMilliseconds);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
ConcurrentQueue<Func<ExplorerClient, Task>> _WhenReady = new ConcurrentQueue<Func<ExplorerClient, Task>>();
|
||||
|
||||
private async Task<StatusResult> GetStatusWithTimeout()
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
@ -162,7 +153,7 @@ namespace BTCPayServer
|
||||
var status = await _Client.GetStatusAsync(cancellation).ConfigureAwait(false);
|
||||
return status;
|
||||
}
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch (OperationCanceledException) { }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
@ -286,7 +286,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
CurrentTime = DateTimeOffset.UtcNow,
|
||||
InvoiceTime = InvoiceTime,
|
||||
ExpirationTime = ExpirationTime,
|
||||
BTCPrice = Money.Coins((decimal)(1.0 / Rate)).ToString(),
|
||||
BTCPrice = Money.Coins((decimal)(ProductInformation.Price / Rate)).ToString(),
|
||||
Status = Status,
|
||||
Url = ServerUrl.WithTrailingSlash() + "invoice?id=" + Id,
|
||||
Currency = ProductInformation.Currency,
|
||||
|
@ -15,10 +15,12 @@ using NBitpayClient;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using BTCPayServer.Events;
|
||||
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
public class InvoiceNotificationManager
|
||||
public class InvoiceNotificationManager : IHostedService
|
||||
{
|
||||
public static HttpClient _Client = new HttpClient();
|
||||
|
||||
@ -41,16 +43,32 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
|
||||
IBackgroundJobClient _JobClient;
|
||||
EventAggregator _EventAggregator;
|
||||
InvoiceRepository _InvoiceRepository;
|
||||
|
||||
public InvoiceNotificationManager(
|
||||
IBackgroundJobClient jobClient,
|
||||
EventAggregator eventAggregator,
|
||||
InvoiceRepository invoiceRepository,
|
||||
ILogger<InvoiceNotificationManager> logger)
|
||||
{
|
||||
Logger = logger as ILogger ?? NullLogger.Instance;
|
||||
_JobClient = jobClient;
|
||||
_EventAggregator = eventAggregator;
|
||||
_InvoiceRepository = invoiceRepository;
|
||||
}
|
||||
|
||||
public void Notify(InvoiceEntity invoice)
|
||||
async Task Notify(InvoiceEntity invoice)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
try
|
||||
{
|
||||
await SendNotification(invoice, cts.Token);
|
||||
return;
|
||||
}
|
||||
catch // It fails, it is OK because we try with hangfire after
|
||||
{
|
||||
}
|
||||
var invoiceStr = NBitcoin.JsonConverters.Serializer.ToString(new ScheduledJob() { TryCount = 0, Invoice = invoice });
|
||||
if (!string.IsNullOrEmpty(invoice.NotificationURL))
|
||||
_JobClient.Schedule(() => NotifyHttp(invoiceStr), TimeSpan.Zero);
|
||||
@ -70,31 +88,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
CancellationTokenSource cts = new CancellationTokenSource(10000);
|
||||
try
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Method = HttpMethod.Post;
|
||||
|
||||
var dto = job.Invoice.EntityToDTO();
|
||||
InvoicePaymentNotification notification = new InvoicePaymentNotification()
|
||||
{
|
||||
Id = dto.Id,
|
||||
Url = dto.Url,
|
||||
BTCDue = dto.BTCDue,
|
||||
BTCPaid = dto.BTCPaid,
|
||||
BTCPrice = dto.BTCPrice,
|
||||
Currency = dto.Currency,
|
||||
CurrentTime = dto.CurrentTime,
|
||||
ExceptionStatus = dto.ExceptionStatus,
|
||||
ExpirationTime = dto.ExpirationTime,
|
||||
InvoiceTime = dto.InvoiceTime,
|
||||
PosData = dto.PosData,
|
||||
Price = dto.Price,
|
||||
Rate = dto.Rate,
|
||||
Status = dto.Status,
|
||||
BuyerFields = job.Invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject() { new JProperty("buyerEmail", job.Invoice.RefundMail) }
|
||||
};
|
||||
request.RequestUri = new Uri(job.Invoice.NotificationURL, UriKind.Absolute);
|
||||
request.Content = new StringContent(JsonConvert.SerializeObject(notification), Encoding.UTF8, "application/json");
|
||||
var response = await _Client.SendAsync(request, cts.Token);
|
||||
HttpResponseMessage response = await SendNotification(job.Invoice, cts.Token);
|
||||
reschedule = response.StatusCode != System.Net.HttpStatusCode.OK;
|
||||
Logger.LogInformation("Job " + jobId + " returned " + response.StatusCode);
|
||||
}
|
||||
@ -116,11 +110,73 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<HttpResponseMessage> SendNotification(InvoiceEntity invoice, CancellationToken cancellation)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Method = HttpMethod.Post;
|
||||
|
||||
var dto = invoice.EntityToDTO();
|
||||
InvoicePaymentNotification notification = new InvoicePaymentNotification()
|
||||
{
|
||||
Id = dto.Id,
|
||||
Url = dto.Url,
|
||||
BTCDue = dto.BTCDue,
|
||||
BTCPaid = dto.BTCPaid,
|
||||
BTCPrice = dto.BTCPrice,
|
||||
Currency = dto.Currency,
|
||||
CurrentTime = dto.CurrentTime,
|
||||
ExceptionStatus = dto.ExceptionStatus,
|
||||
ExpirationTime = dto.ExpirationTime,
|
||||
InvoiceTime = dto.InvoiceTime,
|
||||
PosData = dto.PosData,
|
||||
Price = dto.Price,
|
||||
Rate = dto.Rate,
|
||||
Status = dto.Status,
|
||||
BuyerFields = invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject() { new JProperty("buyerEmail", invoice.RefundMail) }
|
||||
};
|
||||
request.RequestUri = new Uri(invoice.NotificationURL, UriKind.Absolute);
|
||||
request.Content = new StringContent(JsonConvert.SerializeObject(notification), Encoding.UTF8, "application/json");
|
||||
var response = await _Client.SendAsync(request, cancellation);
|
||||
return response;
|
||||
}
|
||||
|
||||
int MaxTry = 6;
|
||||
|
||||
private static string GetHttpJobId(InvoiceEntity invoice)
|
||||
{
|
||||
return $"{invoice.Id}-{invoice.Status}-HTTP";
|
||||
}
|
||||
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
leases.Add(_EventAggregator.Subscribe<InvoiceStatusChangedEvent>(async e =>
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoice(null, e.InvoiceId);
|
||||
|
||||
// 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 (e.NewState == "expired" ||
|
||||
e.NewState == "paid" ||
|
||||
e.NewState == "invalid" ||
|
||||
e.NewState == "complete"
|
||||
)
|
||||
await Notify(invoice);
|
||||
}
|
||||
|
||||
if(e.NewState == "confirmed")
|
||||
{
|
||||
await Notify(invoice);
|
||||
}
|
||||
}));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
leases.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ using Microsoft.Extensions.Hosting;
|
||||
using System.Collections.Concurrent;
|
||||
using Hangfire;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Events;
|
||||
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
@ -25,14 +27,14 @@ namespace BTCPayServer.Services.Invoices
|
||||
InvoiceRepository _InvoiceRepository;
|
||||
ExplorerClient _ExplorerClient;
|
||||
DerivationStrategyFactory _DerivationFactory;
|
||||
InvoiceNotificationManager _NotificationManager;
|
||||
EventAggregator _EventAggregator;
|
||||
BTCPayWallet _Wallet;
|
||||
|
||||
|
||||
public InvoiceWatcher(ExplorerClient explorerClient,
|
||||
InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayWallet wallet,
|
||||
InvoiceNotificationManager notificationManager,
|
||||
InvoiceWatcherAccessor accessor)
|
||||
{
|
||||
LongPollingMode = explorerClient.Network == Network.RegTest;
|
||||
@ -41,23 +43,24 @@ namespace BTCPayServer.Services.Invoices
|
||||
_ExplorerClient = explorerClient ?? throw new ArgumentNullException(nameof(explorerClient));
|
||||
_DerivationFactory = new DerivationStrategyFactory(_ExplorerClient.Network);
|
||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||
_NotificationManager = notificationManager ?? throw new ArgumentNullException(nameof(notificationManager));
|
||||
_EventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
accessor.Instance = this;
|
||||
}
|
||||
CompositeDisposable leases = new CompositeDisposable();
|
||||
|
||||
public bool LongPollingMode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public async Task NotifyReceived(Script scriptPubKey)
|
||||
async Task NotifyReceived(Script scriptPubKey)
|
||||
{
|
||||
var invoice = await _InvoiceRepository.GetInvoiceIdFromScriptPubKey(scriptPubKey);
|
||||
if (invoice != null)
|
||||
_WatchRequests.Add(invoice);
|
||||
}
|
||||
|
||||
public async Task NotifyBlock()
|
||||
async Task NotifyBlock()
|
||||
{
|
||||
foreach (var invoice in await _InvoiceRepository.GetPendingInvoices())
|
||||
{
|
||||
@ -76,17 +79,22 @@ namespace BTCPayServer.Services.Invoices
|
||||
if (invoice == null)
|
||||
break;
|
||||
var stateBefore = invoice.Status;
|
||||
var result = await UpdateInvoice(changes, invoice).ConfigureAwait(false);
|
||||
var postSaveActions = new List<Action>();
|
||||
var result = await UpdateInvoice(changes, invoice, postSaveActions).ConfigureAwait(false);
|
||||
changes = result.Changes;
|
||||
if (result.NeedSave)
|
||||
{
|
||||
await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus).ConfigureAwait(false);
|
||||
|
||||
var changed = stateBefore != invoice.Status;
|
||||
if (changed)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Invoice {invoice.Id}: {stateBefore} => {invoice.Status}");
|
||||
_EventAggregator.Publish(new InvoiceDataChangedEvent() { InvoiceId = invoice.Id });
|
||||
}
|
||||
|
||||
var changed = stateBefore != invoice.Status;
|
||||
|
||||
foreach(var saveAction in postSaveActions)
|
||||
{
|
||||
saveAction();
|
||||
}
|
||||
|
||||
if (invoice.Status == "complete" ||
|
||||
((invoice.Status == "invalid" || invoice.Status == "expired") && invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
|
||||
{
|
||||
@ -111,7 +119,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
|
||||
|
||||
private async Task<(bool NeedSave, UTXOChanges Changes)> UpdateInvoice(UTXOChanges changes, InvoiceEntity invoice)
|
||||
private async Task<(bool NeedSave, UTXOChanges Changes)> UpdateInvoice(UTXOChanges changes, InvoiceEntity invoice, List<Action> postSaveActions)
|
||||
{
|
||||
bool needSave = false;
|
||||
//Fetch unknown payments
|
||||
@ -130,6 +138,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false);
|
||||
invoice.Payments.Add(payment);
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoicePaymentEvent(invoice.Id)));
|
||||
dirtyAddress = true;
|
||||
}
|
||||
//////
|
||||
@ -138,11 +147,9 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
needSave = true;
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoiceStatusChangedEvent(invoice, "expired")));
|
||||
invoice.Status = "expired";
|
||||
if (invoice.FullNotifications)
|
||||
{
|
||||
_NotificationManager.Notify(invoice);
|
||||
}
|
||||
}
|
||||
|
||||
if (invoice.Status == "new" || invoice.Status == "expired")
|
||||
@ -152,11 +159,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
if (invoice.Status == "new")
|
||||
{
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoiceStatusChangedEvent(invoice, "paid")));
|
||||
invoice.Status = "paid";
|
||||
if (invoice.FullNotifications)
|
||||
{
|
||||
_NotificationManager.Notify(invoice);
|
||||
}
|
||||
invoice.ExceptionStatus = null;
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
needSave = true;
|
||||
@ -215,12 +219,9 @@ namespace BTCPayServer.Services.Invoices
|
||||
(chainTotalConfirmed < invoice.GetTotalCryptoDue()))
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoiceStatusChangedEvent(invoice, "invalid")));
|
||||
invoice.Status = "invalid";
|
||||
needSave = true;
|
||||
if (invoice.FullNotifications)
|
||||
{
|
||||
_NotificationManager.Notify(invoice);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -228,8 +229,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
if (totalConfirmed >= invoice.GetTotalCryptoDue())
|
||||
{
|
||||
await _InvoiceRepository.UnaffectAddress(invoice.Id);
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoiceStatusChangedEvent(invoice, "confirmed")));
|
||||
invoice.Status = "confirmed";
|
||||
_NotificationManager.Notify(invoice);
|
||||
needSave = true;
|
||||
}
|
||||
}
|
||||
@ -242,9 +243,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum();
|
||||
if (totalConfirmed >= invoice.GetTotalCryptoDue())
|
||||
{
|
||||
postSaveActions.Add(() => _EventAggregator.Publish(new InvoiceStatusChangedEvent(invoice, "complete")));
|
||||
invoice.Status = "complete";
|
||||
if (invoice.FullNotifications)
|
||||
_NotificationManager.Notify(invoice);
|
||||
needSave = true;
|
||||
}
|
||||
}
|
||||
@ -356,6 +356,10 @@ namespace BTCPayServer.Services.Invoices
|
||||
_WatchRequests.Add(pending);
|
||||
}
|
||||
}, null, 0, (int)PollInterval.TotalMilliseconds);
|
||||
|
||||
leases.Add(_EventAggregator.Subscribe<NewBlockEvent>(async b => { await NotifyBlock(); }));
|
||||
leases.Add(_EventAggregator.Subscribe<TxOutReceivedEvent>(async b => { await NotifyReceived(b.ScriptPubKey); }));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@ -402,6 +406,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
leases.Dispose();
|
||||
_UpdatePendingInvoices.Dispose();
|
||||
_Cts.Cancel();
|
||||
return Task.WhenAny(_RunningTask.Task, Task.Delay(-1, cancellationToken));
|
||||
|
@ -130,7 +130,7 @@
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Bitcoin Core is synching (Chain height: @lastStatus.ChainHeight, Block height: @lastStatus.NodeBlocks)</p>
|
||||
<p>Bitcoin Core is synching (Chain height: @lastStatus.NodeHeaders, Block height: @lastStatus.NodeBlocks)</p>
|
||||
<p>BTCPay Server will not work correctly until it is over.</p>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="@((int)verificationProgress)"
|
||||
|
@ -191,7 +191,7 @@ function onDataCallback(jsonData) {
|
||||
checkoutCtrl.srvModel = jsonData;
|
||||
}
|
||||
|
||||
var watcher = setInterval(function () {
|
||||
function fetchStatus() {
|
||||
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/status";
|
||||
$.ajax({
|
||||
url: path,
|
||||
@ -201,6 +201,27 @@ var watcher = setInterval(function () {
|
||||
}).fail(function (jqXHR, textStatus, errorThrown) {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
|
||||
if (supportsWebSockets) {
|
||||
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/status/ws";
|
||||
path = path.replace("https://", "wss://");
|
||||
path = path.replace("http://", "ws://");
|
||||
try {
|
||||
var socket = new WebSocket(path);
|
||||
socket.onmessage = function (e) {
|
||||
fetchStatus();
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
console.error("Error while connecting to websocket for invoice notifictions");
|
||||
}
|
||||
}
|
||||
|
||||
var watcher = setInterval(function () {
|
||||
fetchStatus();
|
||||
}, 2000);
|
||||
|
||||
$(".menu__item").click(function () {
|
||||
|
Reference in New Issue
Block a user