mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-21 14:04:12 +01:00
Merge pull request #2474 from bolatovumar/feat/test-webhooks
Add ability to send test webhook call
This commit is contained in:
commit
9ecd27dc85
5 changed files with 157 additions and 28 deletions
|
@ -1,3 +1,4 @@
|
|||
#nullable enable
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -14,7 +15,7 @@ namespace BTCPayServer.Controllers
|
|||
[HttpGet("{storeId}/integrations")]
|
||||
public IActionResult Integrations()
|
||||
{
|
||||
return View("Integrations",new IntegrationsViewModel());
|
||||
return View("Integrations", new IntegrationsViewModel());
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/webhooks")]
|
||||
|
@ -109,6 +110,30 @@ namespace BTCPayServer.Controllers
|
|||
return RedirectToAction(nameof(Webhooks), new { storeId = CurrentStore.Id });
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/webhooks/{webhookId}/test")]
|
||||
public async Task<IActionResult> TestWebhook(string webhookId)
|
||||
{
|
||||
var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId);
|
||||
if (webhook is null)
|
||||
return NotFound();
|
||||
|
||||
return View(nameof(TestWebhook));
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/webhooks/{webhookId}/test")]
|
||||
public async Task<IActionResult> TestWebhook(string webhookId, TestWebhookViewModel viewModel)
|
||||
{
|
||||
var result = await WebhookNotificationManager.TestWebhook(CurrentStore.Id, webhookId, viewModel.Type);
|
||||
|
||||
if (result.Success) {
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"{viewModel.Type.ToString()} event delivered successfully! Delivery ID is {result.DeliveryId}";
|
||||
} else {
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"{viewModel.Type.ToString()} event could not be delivered. Error message received: {(result.ErrorMessage ?? "unknown")}";
|
||||
}
|
||||
|
||||
return View(nameof(TestWebhook));
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver")]
|
||||
public async Task<IActionResult> RedeliverWebhook(string webhookId, string deliveryId)
|
||||
{
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -70,7 +71,7 @@ namespace BTCPayServer.HostedServices
|
|||
Subscribe<InvoiceEvent>();
|
||||
}
|
||||
|
||||
public async Task<string> Redeliver(string deliveryId)
|
||||
public async Task<string?> Redeliver(string deliveryId)
|
||||
{
|
||||
var deliveryRequest = await CreateRedeliveryRequest(deliveryId);
|
||||
if (deliveryRequest is null)
|
||||
|
@ -79,7 +80,7 @@ namespace BTCPayServer.HostedServices
|
|||
return deliveryRequest.Delivery.Id;
|
||||
}
|
||||
|
||||
private async Task<WebhookDeliveryRequest> CreateRedeliveryRequest(string deliveryId)
|
||||
private async Task<WebhookDeliveryRequest?> CreateRedeliveryRequest(string deliveryId)
|
||||
{
|
||||
using var ctx = StoreRepository.CreateDbContext();
|
||||
var webhookDelivery = await ctx.WebhookDeliveries.AsNoTracking()
|
||||
|
@ -93,8 +94,7 @@ namespace BTCPayServer.HostedServices
|
|||
if (webhookDelivery is null)
|
||||
return null;
|
||||
var oldDeliveryBlob = webhookDelivery.Delivery.GetBlob();
|
||||
var newDelivery = NewDelivery();
|
||||
newDelivery.WebhookId = webhookDelivery.Webhook.Id;
|
||||
var newDelivery = NewDelivery(webhookDelivery.Webhook.Id);
|
||||
var newDeliveryBlob = new WebhookDeliveryBlob();
|
||||
newDeliveryBlob.Request = oldDeliveryBlob.Request;
|
||||
var webhookEvent = newDeliveryBlob.ReadRequestAs<WebhookEvent>();
|
||||
|
@ -107,6 +107,35 @@ namespace BTCPayServer.HostedServices
|
|||
newDelivery.SetBlob(newDeliveryBlob);
|
||||
return new WebhookDeliveryRequest(webhookDelivery.Webhook.Id, webhookEvent, newDelivery, webhookDelivery.Webhook.GetBlob());
|
||||
}
|
||||
|
||||
private WebhookEvent GetTestWebHook(string storeId, string webhookId, WebhookEventType webhookEventType, Data.WebhookDeliveryData delivery)
|
||||
{
|
||||
var webhookEvent = GetWebhookEvent(webhookEventType);
|
||||
webhookEvent.InvoiceId = "__test__" + Guid.NewGuid().ToString() + "__test__";
|
||||
webhookEvent.StoreId = storeId;
|
||||
webhookEvent.DeliveryId = delivery.Id;
|
||||
webhookEvent.WebhookId = webhookId;
|
||||
webhookEvent.OriginalDeliveryId = "__test__" + Guid.NewGuid().ToString() + "__test__";
|
||||
webhookEvent.IsRedelivery = false;
|
||||
webhookEvent.Timestamp = delivery.Timestamp;
|
||||
|
||||
return webhookEvent;
|
||||
}
|
||||
|
||||
public async Task<DeliveryResult> TestWebhook(string storeId, string webhookId, WebhookEventType webhookEventType)
|
||||
{
|
||||
var delivery = NewDelivery(webhookId);
|
||||
var webhook = (await StoreRepository.GetWebhooks(storeId)).FirstOrDefault(w => w.Id == webhookId);
|
||||
var deliveryRequest = new WebhookDeliveryRequest(
|
||||
webhookId,
|
||||
GetTestWebHook(storeId, webhookId, webhookEventType, delivery),
|
||||
delivery,
|
||||
webhook.GetBlob()
|
||||
);
|
||||
|
||||
return await SendDelivery(deliveryRequest);
|
||||
}
|
||||
|
||||
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
|
||||
{
|
||||
if (evt is InvoiceEvent invoiceEvent)
|
||||
|
@ -119,8 +148,7 @@ namespace BTCPayServer.HostedServices
|
|||
continue;
|
||||
if (!ShouldDeliver(webhookEvent.Type, webhookBlob))
|
||||
continue;
|
||||
Data.WebhookDeliveryData delivery = NewDelivery();
|
||||
delivery.WebhookId = webhook.Id;
|
||||
Data.WebhookDeliveryData delivery = NewDelivery(webhook.Id);
|
||||
webhookEvent.InvoiceId = invoiceEvent.InvoiceId;
|
||||
webhookEvent.StoreId = invoiceEvent.Invoice.StoreId;
|
||||
webhookEvent.DeliveryId = delivery.Id;
|
||||
|
@ -147,7 +175,28 @@ namespace BTCPayServer.HostedServices
|
|||
_ = Process(context.WebhookId, channel);
|
||||
}
|
||||
|
||||
private WebhookInvoiceEvent GetWebhookEvent(InvoiceEvent invoiceEvent)
|
||||
private WebhookInvoiceEvent GetWebhookEvent(WebhookEventType webhookEventType)
|
||||
{
|
||||
switch (webhookEventType)
|
||||
{
|
||||
case WebhookEventType.InvoiceCreated:
|
||||
return new WebhookInvoiceEvent(WebhookEventType.InvoiceCreated);
|
||||
case WebhookEventType.InvoiceReceivedPayment:
|
||||
return new WebhookInvoiceReceivedPaymentEvent(WebhookEventType.InvoiceReceivedPayment);
|
||||
case WebhookEventType.InvoiceProcessing:
|
||||
return new WebhookInvoiceProcessingEvent(WebhookEventType.InvoiceProcessing);
|
||||
case WebhookEventType.InvoiceExpired:
|
||||
return new WebhookInvoiceExpiredEvent(WebhookEventType.InvoiceExpired);
|
||||
case WebhookEventType.InvoiceSettled:
|
||||
return new WebhookInvoiceSettledEvent(WebhookEventType.InvoiceSettled);
|
||||
case WebhookEventType.InvoiceInvalid:
|
||||
return new WebhookInvoiceInvalidEvent(WebhookEventType.InvoiceInvalid);
|
||||
default:
|
||||
return new WebhookInvoiceEvent(WebhookEventType.InvoiceCreated);
|
||||
}
|
||||
}
|
||||
|
||||
private WebhookInvoiceEvent? GetWebhookEvent(InvoiceEvent invoiceEvent)
|
||||
{
|
||||
var eventCode = invoiceEvent.EventCode;
|
||||
switch (eventCode)
|
||||
|
@ -200,7 +249,7 @@ namespace BTCPayServer.HostedServices
|
|||
var wh = (await StoreRepository.GetWebhook(ctx.WebhookId))?.GetBlob();
|
||||
if (wh is null || !ShouldDeliver(ctx.WebhookEvent.Type, wh))
|
||||
continue;
|
||||
var result = await SendDelivery(ctx);
|
||||
var result = await SendAndSaveDelivery(ctx);
|
||||
if (ctx.WebhookBlob.AutomaticRedelivery &&
|
||||
!result.Success &&
|
||||
result.DeliveryId is string)
|
||||
|
@ -208,15 +257,15 @@ namespace BTCPayServer.HostedServices
|
|||
var originalDeliveryId = result.DeliveryId;
|
||||
foreach (var wait in new[]
|
||||
{
|
||||
TimeSpan.FromSeconds(10),
|
||||
TimeSpan.FromMinutes(1),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
})
|
||||
TimeSpan.FromSeconds(10),
|
||||
TimeSpan.FromMinutes(1),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
TimeSpan.FromMinutes(10),
|
||||
})
|
||||
{
|
||||
await Task.Delay(wait, CancellationToken);
|
||||
ctx = await CreateRedeliveryRequest(originalDeliveryId);
|
||||
|
@ -224,7 +273,7 @@ namespace BTCPayServer.HostedServices
|
|||
if (!ctx.WebhookBlob.AutomaticRedelivery ||
|
||||
!ShouldDeliver(ctx.WebhookEvent.Type, ctx.WebhookBlob))
|
||||
break;
|
||||
result = await SendDelivery(ctx);
|
||||
result = await SendAndSaveDelivery(ctx);
|
||||
if (result.Success)
|
||||
break;
|
||||
}
|
||||
|
@ -246,11 +295,13 @@ namespace BTCPayServer.HostedServices
|
|||
return wh.Active && wh.AuthorizedEvents.Match(type);
|
||||
}
|
||||
|
||||
class DeliveryResult
|
||||
public class DeliveryResult
|
||||
{
|
||||
public string DeliveryId { get; set; }
|
||||
public string? DeliveryId { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
private async Task<DeliveryResult> SendDelivery(WebhookDeliveryRequest ctx)
|
||||
{
|
||||
var uri = new Uri(ctx.WebhookBlob.Url, UriKind.Absolute);
|
||||
|
@ -287,8 +338,22 @@ namespace BTCPayServer.HostedServices
|
|||
deliveryBlob.ErrorMessage = ex.Message;
|
||||
}
|
||||
ctx.Delivery.SetBlob(deliveryBlob);
|
||||
|
||||
return new DeliveryResult()
|
||||
{
|
||||
Success = deliveryBlob.ErrorMessage is null,
|
||||
DeliveryId = ctx.Delivery.Id,
|
||||
ErrorMessage = deliveryBlob.ErrorMessage
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private async Task<DeliveryResult> SendAndSaveDelivery(WebhookDeliveryRequest ctx)
|
||||
{
|
||||
var result = await SendDelivery(ctx);
|
||||
await StoreRepository.AddWebhookDelivery(ctx.Delivery);
|
||||
return new DeliveryResult() { Success = deliveryBlob.ErrorMessage is null, DeliveryId = ctx.Delivery.Id };
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] ToBytes(WebhookEvent webhookEvent)
|
||||
|
@ -298,12 +363,14 @@ namespace BTCPayServer.HostedServices
|
|||
return bytes;
|
||||
}
|
||||
|
||||
private static Data.WebhookDeliveryData NewDelivery()
|
||||
private static Data.WebhookDeliveryData NewDelivery(string webhookId)
|
||||
{
|
||||
var delivery = new Data.WebhookDeliveryData();
|
||||
delivery.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16));
|
||||
delivery.Timestamp = DateTimeOffset.UtcNow;
|
||||
return delivery;
|
||||
return new Data.WebhookDeliveryData
|
||||
{
|
||||
Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)),
|
||||
Timestamp = DateTimeOffset.UtcNow,
|
||||
WebhookId = webhookId
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class TestWebhookViewModel
|
||||
{
|
||||
public WebhookEventType Type { get; set; }
|
||||
}
|
||||
}
|
26
BTCPayServer/Views/Stores/TestWebhook.cshtml
Normal file
26
BTCPayServer/Views/Stores/TestWebhook.cshtml
Normal file
|
@ -0,0 +1,26 @@
|
|||
@model EditWebhookViewModel
|
||||
@using BTCPayServer.Client.Models;
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData.SetActivePageAndTitle(StoreNavPages.Webhooks, "Send a test event to a webhook endpoint", Context.GetStoreData().StoreName);
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<form method="post">
|
||||
<h4 class="mb-3">@ViewData["PageTitle"]</h4>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="Type" class="form-label">Event type</label>
|
||||
<select
|
||||
asp-items="Html.GetEnumSelectList<WebhookEventType>()"
|
||||
name="Type"
|
||||
id="Type"
|
||||
class="form-select w-auto"
|
||||
></select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Send test webhook</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -30,7 +30,9 @@
|
|||
<tr>
|
||||
<td class="text-truncate d-block" style="max-width:300px;">@wh.Url</td>
|
||||
<td class="text-end">
|
||||
<a asp-action="ModifyWebhook" asp-route-storeId="@this.Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Modify</a> - <a asp-action="DeleteWebhook" asp-route-storeId="@this.Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Delete</a>
|
||||
<a asp-action="TestWebhook" asp-route-storeId="@this.Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Test</a> -
|
||||
<a asp-action="ModifyWebhook" asp-route-storeId="@this.Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Modify</a> -
|
||||
<a asp-action="DeleteWebhook" asp-route-storeId="@this.Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue