diff --git a/BTCPayServer/HostedServices/Webhooks/PendingTransactionDeliveryRequest.cs b/BTCPayServer/HostedServices/Webhooks/PendingTransactionDeliveryRequest.cs index 8169565dc..8efbc4592 100644 --- a/BTCPayServer/HostedServices/Webhooks/PendingTransactionDeliveryRequest.cs +++ b/BTCPayServer/HostedServices/Webhooks/PendingTransactionDeliveryRequest.cs @@ -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 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; diff --git a/BTCPayServer/HostedServices/Webhooks/PendingTransactionWebhookProvider.cs b/BTCPayServer/HostedServices/Webhooks/PendingTransactionWebhookProvider.cs index a5ae77af3..ece114eef 100644 --- a/BTCPayServer/HostedServices/Webhooks/PendingTransactionWebhookProvider.cs +++ b/BTCPayServer/HostedServices/Webhooks/PendingTransactionWebhookProvider.cs @@ -23,6 +23,7 @@ public class PendingTransactionWebhookProvider : WebhookProvider GetSupportedWebhookTypes() { @@ -30,7 +31,8 @@ public class PendingTransactionWebhookProvider : WebhookProvider new WebhookPendingTransactionEvent( PendingTransactionBroadcast, evt.Data.StoreId), + PendingTransactionService.PendingTransactionEvent.Cancelled => new WebhookPendingTransactionEvent( + PendingTransactionCancelled, evt.Data.StoreId), _ => null }; } diff --git a/BTCPayServer/HostedServices/Webhooks/WebhookExtensions.cs b/BTCPayServer/HostedServices/Webhooks/WebhookExtensions.cs index 4112c54e8..2900734f0 100644 --- a/BTCPayServer/HostedServices/Webhooks/WebhookExtensions.cs +++ b/BTCPayServer/HostedServices/Webhooks/WebhookExtensions.cs @@ -39,6 +39,10 @@ public static class WebhookExtensions services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); services.AddHostedService(o => o.GetRequiredService()); + + services.AddSingleton(); + services.AddSingleton(o => o.GetRequiredService()); + services.AddHostedService(o => o.GetRequiredService()); services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 12303ac58..3614df244 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -354,8 +354,8 @@ namespace BTCPayServer.Hosting services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); - services.AddSingleton(o => o.GetRequiredService()); services.AddScheduledTask(TimeSpan.FromMinutes(10)); + // PendingTransactionWebhookProvider webhooks registered in WebhookExtensions services.TryAddSingleton(); services.AddSingleton(provider => provider.GetService()); diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml index 563d2a749..d1e3bed90 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml @@ -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 @@ {Payout.Metadata}* + + Pending Transaction + + {PendingTransaction.Id}, + {PendingTransaction.StoreId}, + {PendingTransaction.SignaturesCollected}, + {PendingTransaction.SignaturesNeeded}, + {PendingTransaction.SignaturesTotal} + + * These fields are JSON objects. You can access properties within them using this syntax. One example is {Invoice.Metadata.itemCode} @@ -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');