Triggering webhook for pending transaction events

Adding Cancelled event as well
This commit is contained in:
rockstardev
2025-03-23 19:18:46 -05:00
parent 6784be2ce2
commit dd17cd25cb
5 changed files with 57 additions and 15 deletions

View File

@@ -1,15 +1,17 @@
using System.Globalization;
using System.Collections;
using System.Globalization;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Wallets;
using WebhookDeliveryData = BTCPayServer.Data.WebhookDeliveryData;
namespace BTCPayServer.HostedServices.Webhooks;
public class PendingTransactionDeliveryRequest(
PendingTransaction pendingTransaction,
PendingTransactionService.PendingTransactionEvent evt,
string webhookId,
WebhookEvent webhookEvent,
WebhookDeliveryData delivery,
@@ -19,6 +21,7 @@ public class PendingTransactionDeliveryRequest(
public override Task<SendEmailRequest> Interpolate(SendEmailRequest req,
UIStoresController.StoreEmailRule storeEmailRule)
{
var blob = evt.Data.GetBlob();
// if (storeEmailRule.CustomerEmail &&
// MailboxAddressValidator.TryParse(Invoice.Metadata.BuyerEmail, out var bmb))
// {
@@ -26,20 +29,18 @@ public class PendingTransactionDeliveryRequest(
// req.Email += $",{bmb}";
// }
req.Subject = Interpolate(req.Subject);
req.Body = Interpolate(req.Body);
req.Subject = Interpolate(req.Subject, blob);
req.Body = Interpolate(req.Body, blob);
return Task.FromResult(req);
}
private string Interpolate(string str)
private string Interpolate(string str, PendingTransactionBlob blob)
{
var res = str.Replace("{PendingTransaction.Id}", pendingTransaction.TransactionId);
// .Replace("{Invoice.StoreId}", Invoice.StoreId)
// .Replace("{Invoice.Price}", Invoice.Price.ToString(CultureInfo.InvariantCulture))
// .Replace("{Invoice.Currency}", Invoice.Currency)
// .Replace("{Invoice.Status}", Invoice.Status.ToString())
// .Replace("{Invoice.AdditionalStatus}", Invoice.ExceptionStatus.ToString())
// .Replace("{Invoice.OrderId}", Invoice.Metadata.OrderId);
var res = str.Replace("{PendingTransaction.Id}", evt.Data.TransactionId)
.Replace("{PendingTransaction.StoreId}", evt.Data.StoreId)
.Replace("{PendingTransaction.SignaturesCollected}", blob.SignaturesCollected?.ToString())
.Replace("{PendingTransaction.SignaturesNeeded}", blob.SignaturesNeeded?.ToString())
.Replace("{PendingTransaction.SignaturesTotal}", blob.SignaturesTotal?.ToString());
// res = InterpolateJsonField(res, "Invoice.Metadata", Invoice.Metadata.ToJObject());
return res;

View File

@@ -23,6 +23,7 @@ public class PendingTransactionWebhookProvider : WebhookProvider<PendingTransact
public const string PendingTransactionCreated = nameof(PendingTransactionCreated);
public const string PendingTransactionSignatureCollected = nameof(PendingTransactionSignatureCollected);
public const string PendingTransactionBroadcast = nameof(PendingTransactionBroadcast);
public const string PendingTransactionCancelled = nameof(PendingTransactionCancelled);
public override Dictionary<string, string> GetSupportedWebhookTypes()
{
@@ -30,7 +31,8 @@ public class PendingTransactionWebhookProvider : WebhookProvider<PendingTransact
{
{PendingTransactionCreated, "Pending Transaction - Created"},
{PendingTransactionSignatureCollected, "Pending Transaction - Signature Collected"},
{PendingTransactionBroadcast, "Pending Transaction - Broadcast"}
{PendingTransactionBroadcast, "Pending Transaction - Broadcast"},
{PendingTransactionCancelled, "Pending Transaction - Cancelled"}
};
}
@@ -50,7 +52,7 @@ public class PendingTransactionWebhookProvider : WebhookProvider<PendingTransact
webhookEvent.OriginalDeliveryId = delivery.Id;
webhookEvent.Timestamp = delivery.Timestamp;
}
return new PendingTransactionDeliveryRequest(evt.Data, webhook?.Id, webhookEvent, delivery, webhookBlob);
return new PendingTransactionDeliveryRequest(evt, webhook?.Id, webhookEvent, delivery, webhookBlob);
}
protected override WebhookPendingTransactionEvent GetWebhookEvent(PendingTransactionService.PendingTransactionEvent evt)
@@ -63,6 +65,8 @@ public class PendingTransactionWebhookProvider : WebhookProvider<PendingTransact
PendingTransactionSignatureCollected, evt.Data.StoreId),
PendingTransactionService.PendingTransactionEvent.Broadcast => new WebhookPendingTransactionEvent(
PendingTransactionBroadcast, evt.Data.StoreId),
PendingTransactionService.PendingTransactionEvent.Cancelled => new WebhookPendingTransactionEvent(
PendingTransactionCancelled, evt.Data.StoreId),
_ => null
};
}

View File

@@ -39,6 +39,10 @@ public static class WebhookExtensions
services.AddSingleton<PaymentRequestWebhookProvider>();
services.AddSingleton<IWebhookProvider>(o => o.GetRequiredService<PaymentRequestWebhookProvider>());
services.AddHostedService(o => o.GetRequiredService<PaymentRequestWebhookProvider>());
services.AddSingleton<PendingTransactionWebhookProvider>();
services.AddSingleton<IWebhookProvider>(o => o.GetRequiredService<PendingTransactionWebhookProvider>());
services.AddHostedService(o => o.GetRequiredService<PendingTransactionWebhookProvider>());
services.AddSingleton<WebhookSender>();
services.AddSingleton<IHostedService, WebhookSender>(o => o.GetRequiredService<WebhookSender>());

View File

@@ -354,8 +354,8 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<PaymentRequestRepository>();
services.TryAddSingleton<BTCPayWalletProvider>();
services.AddSingleton<PendingTransactionService>();
services.AddSingleton<IWebhookProvider>(o => o.GetRequiredService<PendingTransactionWebhookProvider>());
services.AddScheduledTask<PendingTransactionService>(TimeSpan.FromMinutes(10));
// PendingTransactionWebhookProvider webhooks registered in WebhookExtensions
services.TryAddSingleton<WalletReceiveService>();
services.AddSingleton<IHostedService>(provider => provider.GetService<WalletReceiveService>());

View File

@@ -1,12 +1,16 @@
@using BTCPayServer.HostedServices.Webhooks
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.Controllers.UIStoresController.StoreEmailRule
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor context;
@inject WebhookSender WebhookSender
@{
var storeId = Context.GetStoreData().Id;
bool isEdit = Model.Trigger != null;
ViewData.SetActivePage(StoreNavPages.Emails, StringLocalizer[isEdit ? "Edit Email Rule" : "Create Email Rule"], storeId);
var expectedScheme = context.HttpContext?.Request.Scheme;
var expectedHost = context.HttpContext?.Request.Host.ToString().ToLower();
var hostRoot = $"{expectedScheme}://{expectedHost}";
}
@section PageHeadContent {
@@ -104,6 +108,16 @@
<code>{Payout.Metadata}*</code>
</td>
</tr>
<tr>
<th text-translate="true">Pending Transaction</th>
<td>
<code>{PendingTransaction.Id}</code>,
<code>{PendingTransaction.StoreId}</code>,
<code>{PendingTransaction.SignaturesCollected}</code>,
<code>{PendingTransaction.SignaturesNeeded}</code>,
<code>{PendingTransaction.SignaturesTotal}</code>
</td>
</tr>
<tr><td colspan="2">* These fields are JSON objects. You can access properties within them using <a href="https://www.newtonsoft.com/json/help/html/SelectToken.htm#SelectTokenJSONPath" rel="noreferrer noopener" target="_blank">this syntax</a>. One example is <code>{Invoice.Metadata.itemCode}</code></td></tr>
</table>
</div>
@@ -145,6 +159,25 @@
subject: 'Invoice {Invoice.Id} payment settled',
body: 'Invoice {Invoice.Id} (Order Id: {Invoice.OrderId}) payment settled.'
},
@PendingTransactionWebhookProvider.PendingTransactionCreated : {
subject: 'Pending Transaction {PendingTransaction.Id} Created',
body: 'Review the transaction and sign it on: @hostRoot/wallets/S-{PendingTransaction.StoreId}-BTC/transactions'
},
@PendingTransactionWebhookProvider.PendingTransactionSignatureCollected : {
subject: 'Signature Collected for Pending Transaction {PendingTransaction.Id}',
body: 'So far {PendingTransaction.SignaturesCollected} signatures collected out of {PendingTransaction.SignaturesNeeded} signatures needed. ' +
'Review the transaction and sign it on: @hostRoot/wallets/S-{PendingTransaction.StoreId}-BTC/transactions'
},
@PendingTransactionWebhookProvider.PendingTransactionBroadcast : {
subject: 'Transaction {PendingTransaction.Id} has been Broadcast',
body: 'Transaction is visible in mempool on: https://mempool.space/tx/{PendingTransaction.Id}. ' +
'Review the transaction: @hostRoot/wallets/S-{PendingTransaction.StoreId}-BTC/transactions'
},
@PendingTransactionWebhookProvider.PendingTransactionCancelled : {
subject: 'Pending Transaction {PendingTransaction.Id} Cancelled',
body: 'Transaction {PendingTransaction.Id} is cancelled and signatures are no longer being collected. ' +
'Review the wallet: @hostRoot/wallets/S-{PendingTransaction.StoreId}-BTC/transactions'
},
};
const triggerSelect = document.querySelector('.email-rule-trigger');