2019-01-14 22:43:29 +01:00
|
|
|
using System;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
2022-03-02 18:28:12 +01:00
|
|
|
using BTCPayServer.Abstractions.Extensions;
|
2019-01-14 22:43:29 +01:00
|
|
|
using BTCPayServer.Controllers;
|
2020-06-28 10:55:27 +02:00
|
|
|
using BTCPayServer.Data;
|
2019-01-14 22:43:29 +01:00
|
|
|
using BTCPayServer.Events;
|
|
|
|
using BTCPayServer.HostedServices;
|
|
|
|
using BTCPayServer.Logging;
|
2024-04-04 09:31:04 +02:00
|
|
|
using BTCPayServer.Services;
|
2019-01-14 22:43:29 +01:00
|
|
|
using BTCPayServer.Services.PaymentRequests;
|
2020-06-28 10:55:27 +02:00
|
|
|
using Microsoft.AspNetCore.Builder;
|
2019-03-09 08:08:31 +01:00
|
|
|
using Microsoft.AspNetCore.Http;
|
2020-06-28 10:55:27 +02:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2019-10-03 11:46:09 +02:00
|
|
|
using Microsoft.AspNetCore.Routing;
|
2020-06-28 10:55:27 +02:00
|
|
|
using Microsoft.AspNetCore.SignalR;
|
|
|
|
using Microsoft.Extensions.Logging;
|
2020-05-19 19:59:23 +02:00
|
|
|
using PaymentRequestData = BTCPayServer.Client.Models.PaymentRequestData;
|
2019-01-14 22:43:29 +01:00
|
|
|
|
|
|
|
namespace BTCPayServer.PaymentRequest
|
|
|
|
{
|
|
|
|
public class PaymentRequestHub : Hub
|
|
|
|
{
|
2022-01-07 04:32:00 +01:00
|
|
|
private readonly UIPaymentRequestController _PaymentRequestController;
|
2019-01-14 22:43:29 +01:00
|
|
|
public const string InvoiceCreated = "InvoiceCreated";
|
2023-09-19 03:10:13 +02:00
|
|
|
public const string InvoiceConfirmed = "InvoiceConfirmed";
|
2019-01-14 22:43:29 +01:00
|
|
|
public const string PaymentReceived = "PaymentReceived";
|
|
|
|
public const string InfoUpdated = "InfoUpdated";
|
|
|
|
public const string InvoiceError = "InvoiceError";
|
2019-05-07 10:26:40 +02:00
|
|
|
public const string CancelInvoiceError = "CancelInvoiceError";
|
|
|
|
public const string InvoiceCancelled = "InvoiceCancelled";
|
2019-01-14 22:43:29 +01:00
|
|
|
|
2022-01-07 04:32:00 +01:00
|
|
|
public PaymentRequestHub(UIPaymentRequestController paymentRequestController)
|
2019-01-14 22:43:29 +01:00
|
|
|
{
|
|
|
|
_PaymentRequestController = paymentRequestController;
|
|
|
|
}
|
|
|
|
|
2022-10-27 08:50:35 +02:00
|
|
|
public async Task ListenToPaymentRequest(string prId)
|
2019-01-14 22:43:29 +01:00
|
|
|
{
|
|
|
|
if (Context.Items.ContainsKey("pr-id"))
|
|
|
|
{
|
|
|
|
await Groups.RemoveFromGroupAsync(Context.ConnectionId, Context.Items["pr-id"].ToString());
|
|
|
|
Context.Items.Remove("pr-id");
|
|
|
|
}
|
2022-10-27 08:50:35 +02:00
|
|
|
if (prId != null)
|
|
|
|
{
|
|
|
|
Context.Items.Add("pr-id", prId);
|
|
|
|
await Groups.AddToGroupAsync(Context.ConnectionId, prId);
|
|
|
|
}
|
2019-01-14 22:43:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-10-27 08:50:35 +02:00
|
|
|
public async Task Pay(string prId, decimal? amount = null)
|
2019-01-14 22:43:29 +01:00
|
|
|
{
|
2022-10-27 08:50:35 +02:00
|
|
|
if (prId is null)
|
|
|
|
return;
|
2019-01-14 22:43:29 +01:00
|
|
|
_PaymentRequestController.ControllerContext.HttpContext = Context.GetHttpContext();
|
2019-02-22 11:37:45 +01:00
|
|
|
var result =
|
2022-10-27 08:50:35 +02:00
|
|
|
await _PaymentRequestController.PayPaymentRequest(prId, false, amount);
|
2019-01-14 22:43:29 +01:00
|
|
|
switch (result)
|
|
|
|
{
|
|
|
|
case OkObjectResult okObjectResult:
|
2020-06-28 10:55:27 +02:00
|
|
|
await Clients.Caller.SendCoreAsync(InvoiceCreated, new[] { okObjectResult.Value.ToString() });
|
2019-01-14 22:43:29 +01:00
|
|
|
break;
|
|
|
|
case ObjectResult objectResult:
|
2020-06-28 10:55:27 +02:00
|
|
|
await Clients.Caller.SendCoreAsync(InvoiceError, new[] { objectResult.Value });
|
2019-01-14 22:43:29 +01:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
await Clients.Caller.SendCoreAsync(InvoiceError, System.Array.Empty<object>());
|
|
|
|
break;
|
|
|
|
}
|
2019-05-07 10:26:40 +02:00
|
|
|
}
|
|
|
|
|
2022-10-27 08:50:35 +02:00
|
|
|
public async Task CancelUnpaidPendingInvoice(string prId)
|
2019-05-07 10:26:40 +02:00
|
|
|
{
|
2022-10-27 08:50:35 +02:00
|
|
|
if (prId is null)
|
|
|
|
return;
|
2019-05-07 10:26:40 +02:00
|
|
|
_PaymentRequestController.ControllerContext.HttpContext = Context.GetHttpContext();
|
|
|
|
var result =
|
2022-10-27 08:50:35 +02:00
|
|
|
await _PaymentRequestController.CancelUnpaidPendingInvoice(prId, false);
|
2019-05-07 10:26:40 +02:00
|
|
|
switch (result)
|
|
|
|
{
|
2024-01-18 01:47:39 +01:00
|
|
|
case OkObjectResult:
|
2022-10-27 08:50:35 +02:00
|
|
|
await Clients.Group(prId).SendCoreAsync(InvoiceCancelled, System.Array.Empty<object>());
|
2019-05-07 10:26:40 +02:00
|
|
|
break;
|
2020-06-28 10:55:27 +02:00
|
|
|
|
2019-05-07 10:26:40 +02:00
|
|
|
default:
|
|
|
|
await Clients.Caller.SendCoreAsync(CancelInvoiceError, System.Array.Empty<object>());
|
|
|
|
break;
|
|
|
|
}
|
2019-01-14 22:43:29 +01:00
|
|
|
}
|
2019-03-09 08:08:31 +01:00
|
|
|
|
|
|
|
public static string GetHubPath(HttpRequest request)
|
|
|
|
{
|
|
|
|
return request.GetRelativePathOrAbsolute("/payment-requests/hub");
|
|
|
|
}
|
2019-10-03 11:46:09 +02:00
|
|
|
|
|
|
|
public static void Register(IEndpointRouteBuilder route)
|
2019-03-09 08:08:31 +01:00
|
|
|
{
|
|
|
|
route.MapHub<PaymentRequestHub>("/payment-requests/hub");
|
|
|
|
}
|
2019-01-14 22:43:29 +01:00
|
|
|
}
|
|
|
|
|
2019-02-22 11:37:45 +01:00
|
|
|
public class PaymentRequestStreamer : EventHostedServiceBase
|
2019-01-14 22:43:29 +01:00
|
|
|
{
|
|
|
|
private readonly IHubContext<PaymentRequestHub> _HubContext;
|
|
|
|
private readonly PaymentRequestRepository _PaymentRequestRepository;
|
2024-04-04 09:31:04 +02:00
|
|
|
private readonly PrettyNameProvider _prettyNameProvider;
|
2019-01-14 22:43:29 +01:00
|
|
|
private readonly PaymentRequestService _PaymentRequestService;
|
|
|
|
|
2019-02-22 11:37:45 +01:00
|
|
|
|
2019-01-14 22:43:29 +01:00
|
|
|
public PaymentRequestStreamer(EventAggregator eventAggregator,
|
|
|
|
IHubContext<PaymentRequestHub> hubContext,
|
|
|
|
PaymentRequestRepository paymentRequestRepository,
|
2024-04-04 09:31:04 +02:00
|
|
|
PrettyNameProvider prettyNameProvider,
|
2021-11-22 09:16:08 +01:00
|
|
|
PaymentRequestService paymentRequestService,
|
|
|
|
Logs logs) : base(eventAggregator, logs)
|
2019-01-14 22:43:29 +01:00
|
|
|
{
|
|
|
|
_HubContext = hubContext;
|
|
|
|
_PaymentRequestRepository = paymentRequestRepository;
|
2024-04-04 09:31:04 +02:00
|
|
|
_prettyNameProvider = prettyNameProvider;
|
2019-01-14 22:43:29 +01:00
|
|
|
_PaymentRequestService = paymentRequestService;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override async Task StartAsync(CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
await base.StartAsync(cancellationToken);
|
2019-02-22 11:37:45 +01:00
|
|
|
_CheckingPendingPayments = CheckingPendingPayments(cancellationToken)
|
|
|
|
.ContinueWith(_ => _CheckingPendingPayments = null, TaskScheduler.Default);
|
2019-01-14 22:43:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private async Task CheckingPendingPayments(CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
Logs.PayServer.LogInformation("Starting payment request expiration watcher");
|
2023-09-19 03:10:13 +02:00
|
|
|
var items = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery
|
2019-01-14 22:43:29 +01:00
|
|
|
{
|
2023-09-19 03:10:13 +02:00
|
|
|
Status = new[]
|
|
|
|
{
|
|
|
|
PaymentRequestData.PaymentRequestStatus.Pending,
|
|
|
|
PaymentRequestData.PaymentRequestStatus.Processing
|
|
|
|
}
|
2019-01-14 22:43:29 +01:00
|
|
|
}, cancellationToken);
|
2022-05-02 09:35:28 +02:00
|
|
|
Logs.PayServer.LogInformation($"{items.Length} pending payment requests being checked since last run");
|
2019-02-22 11:37:45 +01:00
|
|
|
await Task.WhenAll(items.Select(i => _PaymentRequestService.UpdatePaymentRequestStateIfNeeded(i))
|
|
|
|
.ToArray());
|
2019-01-14 22:43:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Task _CheckingPendingPayments;
|
2019-02-22 11:37:45 +01:00
|
|
|
|
2019-01-14 22:43:29 +01:00
|
|
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
await base.StopAsync(cancellationToken);
|
|
|
|
await (_CheckingPendingPayments ?? Task.CompletedTask);
|
|
|
|
}
|
2019-02-22 11:37:45 +01:00
|
|
|
|
2020-04-28 08:06:28 +02:00
|
|
|
protected override void SubscribeToEvents()
|
2019-01-14 22:43:29 +01:00
|
|
|
{
|
|
|
|
Subscribe<InvoiceEvent>();
|
|
|
|
Subscribe<PaymentRequestUpdated>();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
if (evt is InvoiceEvent invoiceEvent)
|
|
|
|
{
|
2019-02-25 08:15:45 +01:00
|
|
|
foreach (var paymentId in PaymentRequestRepository.GetPaymentIdsFromInternalTags(invoiceEvent.Invoice))
|
2019-01-14 22:43:29 +01:00
|
|
|
{
|
2023-09-19 03:10:13 +02:00
|
|
|
if (invoiceEvent.Name is InvoiceEvent.ReceivedPayment or InvoiceEvent.MarkedCompleted or InvoiceEvent.MarkedInvalid)
|
2019-02-25 08:15:45 +01:00
|
|
|
{
|
|
|
|
await _PaymentRequestService.UpdatePaymentRequestStateIfNeeded(paymentId);
|
2024-04-04 09:31:04 +02:00
|
|
|
if (invoiceEvent.Payment != null)
|
2021-07-28 15:38:26 +02:00
|
|
|
{
|
|
|
|
await _HubContext.Clients.Group(paymentId).SendCoreAsync(PaymentRequestHub.PaymentReceived,
|
|
|
|
new object[]
|
|
|
|
{
|
2024-04-04 09:31:04 +02:00
|
|
|
invoiceEvent.Payment.Value,
|
2023-07-19 11:47:32 +02:00
|
|
|
invoiceEvent.Payment.Currency,
|
2024-04-04 09:31:04 +02:00
|
|
|
_prettyNameProvider.PrettyName(invoiceEvent.Payment.PaymentMethodId),
|
|
|
|
invoiceEvent.Payment.PaymentMethodId.ToString()
|
2021-07-28 15:38:26 +02:00
|
|
|
}, cancellationToken);
|
|
|
|
}
|
2019-02-25 08:15:45 +01:00
|
|
|
}
|
2023-09-19 03:10:13 +02:00
|
|
|
else if (invoiceEvent.Name is InvoiceEvent.Completed or InvoiceEvent.Confirmed)
|
|
|
|
{
|
|
|
|
await _PaymentRequestService.UpdatePaymentRequestStateIfNeeded(paymentId);
|
|
|
|
await _HubContext.Clients.Group(paymentId).SendCoreAsync(PaymentRequestHub.InvoiceConfirmed,
|
|
|
|
new object[]
|
|
|
|
{
|
|
|
|
invoiceEvent.InvoiceId
|
|
|
|
}, cancellationToken);
|
|
|
|
}
|
2019-02-22 11:37:45 +01:00
|
|
|
|
2019-02-25 08:15:45 +01:00
|
|
|
await InfoUpdated(paymentId);
|
|
|
|
}
|
2019-01-14 22:43:29 +01:00
|
|
|
}
|
|
|
|
else if (evt is PaymentRequestUpdated updated)
|
|
|
|
{
|
2021-07-28 15:38:26 +02:00
|
|
|
await _PaymentRequestService.UpdatePaymentRequestStateIfNeeded(updated.PaymentRequestId);
|
2019-02-22 11:37:45 +01:00
|
|
|
await InfoUpdated(updated.PaymentRequestId);
|
|
|
|
|
2023-09-19 03:10:13 +02:00
|
|
|
var isPending = updated.Data.Status is
|
|
|
|
PaymentRequestData.PaymentRequestStatus.Pending or
|
|
|
|
PaymentRequestData.PaymentRequestStatus.Processing;
|
2019-01-14 22:43:29 +01:00
|
|
|
var expiry = updated.Data.GetBlob().ExpiryDate;
|
2023-09-19 03:10:13 +02:00
|
|
|
if (isPending && expiry.HasValue)
|
2019-01-14 22:43:29 +01:00
|
|
|
{
|
|
|
|
QueueExpiryTask(
|
|
|
|
updated.PaymentRequestId,
|
2022-01-11 10:42:44 +01:00
|
|
|
expiry.Value.UtcDateTime,
|
2019-01-14 22:43:29 +01:00
|
|
|
cancellationToken);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void QueueExpiryTask(string paymentRequestId, DateTime expiry, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
Task.Run(async () =>
|
|
|
|
{
|
2021-12-17 07:31:06 +01:00
|
|
|
var delay = expiry - DateTime.UtcNow;
|
2019-01-14 22:43:29 +01:00
|
|
|
if (delay > TimeSpan.Zero)
|
|
|
|
await Task.Delay(delay, cancellationToken);
|
|
|
|
await _PaymentRequestService.UpdatePaymentRequestStateIfNeeded(paymentRequestId);
|
|
|
|
}, cancellationToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
private async Task InfoUpdated(string paymentRequestId)
|
|
|
|
{
|
|
|
|
var req = await _PaymentRequestService.GetPaymentRequest(paymentRequestId);
|
2019-02-22 11:37:45 +01:00
|
|
|
if (req != null)
|
2019-01-14 22:43:29 +01:00
|
|
|
{
|
|
|
|
await _HubContext.Clients.Group(paymentRequestId)
|
2020-06-28 10:55:27 +02:00
|
|
|
.SendCoreAsync(PaymentRequestHub.InfoUpdated, new object[] { req });
|
2019-01-14 22:43:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|