mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 09:54:30 +01:00
Use websockets in checkout page to get notified of paid invoices
This commit is contained in:
parent
9d7f5b5b6e
commit
aaadda3e0f
@ -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)
|
||||
|
@ -29,13 +29,11 @@ namespace BTCPayServer.Hosting
|
||||
{
|
||||
TokenRepository _TokenRepository;
|
||||
RequestDelegate _Next;
|
||||
CallbackController _CallbackController;
|
||||
BTCPayServerOptions _Options;
|
||||
|
||||
public BTCPayMiddleware(RequestDelegate next,
|
||||
TokenRepository tokenRepo,
|
||||
BTCPayServerOptions options,
|
||||
CallbackController callbackController)
|
||||
BTCPayServerOptions options)
|
||||
{
|
||||
_TokenRepository = tokenRepo ?? throw new ArgumentNullException(nameof(tokenRepo));
|
||||
_Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||
@ -43,12 +41,9 @@ namespace BTCPayServer.Hosting
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool _Registered;
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
RewriteHostIfNeeded(httpContext);
|
||||
|
||||
httpContext.Request.Headers.TryGetValue("x-signature", out StringValues values);
|
||||
var sig = values.FirstOrDefault();
|
||||
httpContext.Request.Headers.TryGetValue("x-identity", out values);
|
||||
|
@ -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(
|
||||
|
@ -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,21 @@ 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://");
|
||||
var socket = new WebSocket(path);
|
||||
socket.onmessage = function (e) {
|
||||
fetchStatus();
|
||||
};
|
||||
}
|
||||
|
||||
var watcher = setInterval(function () {
|
||||
fetchStatus();
|
||||
}, 2000);
|
||||
|
||||
$(".menu__item").click(function () {
|
||||
|
Loading…
Reference in New Issue
Block a user