mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Use websockets in checkout page to get notified of paid invoices
This commit is contained in:
@@ -16,12 +16,14 @@ using System.Security.Claims;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Threading;
|
||||||
|
using BTCPayServer.Events;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
public partial class InvoiceController
|
public partial class InvoiceController
|
||||||
{
|
{
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("invoices/{invoiceId}")]
|
[Route("invoices/{invoiceId}")]
|
||||||
public IActionResult Invoice(string invoiceId, string command)
|
public IActionResult Invoice(string invoiceId, string command)
|
||||||
@@ -169,6 +171,73 @@ namespace BTCPayServer.Controllers
|
|||||||
return Json(model);
|
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]
|
[HttpPost]
|
||||||
[Route("i/{invoiceId}/UpdateCustomer")]
|
[Route("i/{invoiceId}/UpdateCustomer")]
|
||||||
public async Task<IActionResult> UpdateCustomer(string invoiceId, [FromBody]UpdateCustomerModel data)
|
public async Task<IActionResult> UpdateCustomer(string invoiceId, [FromBody]UpdateCustomerModel data)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ namespace BTCPayServer.Controllers
|
|||||||
IFeeProvider _FeeProvider;
|
IFeeProvider _FeeProvider;
|
||||||
private CurrencyNameTable _CurrencyNameTable;
|
private CurrencyNameTable _CurrencyNameTable;
|
||||||
ExplorerClient _Explorer;
|
ExplorerClient _Explorer;
|
||||||
|
EventAggregator _EventAggregator;
|
||||||
public InvoiceController(
|
public InvoiceController(
|
||||||
Network network,
|
Network network,
|
||||||
InvoiceRepository invoiceRepository,
|
InvoiceRepository invoiceRepository,
|
||||||
@@ -62,6 +62,7 @@ namespace BTCPayServer.Controllers
|
|||||||
BTCPayWallet wallet,
|
BTCPayWallet wallet,
|
||||||
IRateProvider rateProvider,
|
IRateProvider rateProvider,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
|
EventAggregator eventAggregator,
|
||||||
InvoiceWatcherAccessor watcher,
|
InvoiceWatcherAccessor watcher,
|
||||||
ExplorerClient explorerClient,
|
ExplorerClient explorerClient,
|
||||||
IFeeProvider feeProvider)
|
IFeeProvider feeProvider)
|
||||||
@@ -76,6 +77,7 @@ namespace BTCPayServer.Controllers
|
|||||||
_Watcher = (watcher ?? throw new ArgumentNullException(nameof(watcher))).Instance;
|
_Watcher = (watcher ?? throw new ArgumentNullException(nameof(watcher))).Instance;
|
||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
_FeeProvider = feeProvider ?? throw new ArgumentNullException(nameof(feeProvider));
|
_FeeProvider = feeProvider ?? throw new ArgumentNullException(nameof(feeProvider));
|
||||||
|
_EventAggregator = eventAggregator;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15)
|
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15)
|
||||||
|
|||||||
@@ -29,13 +29,11 @@ namespace BTCPayServer.Hosting
|
|||||||
{
|
{
|
||||||
TokenRepository _TokenRepository;
|
TokenRepository _TokenRepository;
|
||||||
RequestDelegate _Next;
|
RequestDelegate _Next;
|
||||||
CallbackController _CallbackController;
|
|
||||||
BTCPayServerOptions _Options;
|
BTCPayServerOptions _Options;
|
||||||
|
|
||||||
public BTCPayMiddleware(RequestDelegate next,
|
public BTCPayMiddleware(RequestDelegate next,
|
||||||
TokenRepository tokenRepo,
|
TokenRepository tokenRepo,
|
||||||
BTCPayServerOptions options,
|
BTCPayServerOptions options)
|
||||||
CallbackController callbackController)
|
|
||||||
{
|
{
|
||||||
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
||||||
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||||
@@ -43,12 +41,9 @@ namespace BTCPayServer.Hosting
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool _Registered;
|
|
||||||
public async Task Invoke(HttpContext httpContext)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
RewriteHostIfNeeded(httpContext);
|
RewriteHostIfNeeded(httpContext);
|
||||||
|
|
||||||
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
||||||
var sig = values.FirstOrDefault();
|
var sig = values.FirstOrDefault();
|
||||||
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ namespace BTCPayServer.Hosting
|
|||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseHangfireServer();
|
app.UseHangfireServer();
|
||||||
app.UseHangfireDashboard("/hangfire", new DashboardOptions() { Authorization = new[] { new NeedRole(Roles.ServerAdmin) } });
|
app.UseHangfireDashboard("/hangfire", new DashboardOptions() { Authorization = new[] { new NeedRole(Roles.ServerAdmin) } });
|
||||||
|
app.UseWebSockets();
|
||||||
app.UseMvc(routes =>
|
app.UseMvc(routes =>
|
||||||
{
|
{
|
||||||
routes.MapRoute(
|
routes.MapRoute(
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ function onDataCallback(jsonData) {
|
|||||||
checkoutCtrl.srvModel = jsonData;
|
checkoutCtrl.srvModel = jsonData;
|
||||||
}
|
}
|
||||||
|
|
||||||
var watcher = setInterval(function () {
|
function fetchStatus() {
|
||||||
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/status";
|
var path = srvModel.serverUrl + "/i/" + srvModel.invoiceId + "/status";
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: path,
|
url: path,
|
||||||
@@ -201,6 +201,21 @@ var watcher = setInterval(function () {
|
|||||||
}).fail(function (jqXHR, textStatus, errorThrown) {
|
}).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://");
|
||||||
|
var socket = new WebSocket(path);
|
||||||
|
socket.onmessage = function (e) {
|
||||||
|
fetchStatus();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var watcher = setInterval(function () {
|
||||||
|
fetchStatus();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
$(".menu__item").click(function () {
|
$(".menu__item").click(function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user