From f0b0aa0c899557d8b780ffd82a95bb0f7b924a0e Mon Sep 17 00:00:00 2001
From: rockstardev <5191402+rockstardev@users.noreply.github.com>
Date: Tue, 25 Feb 2025 01:31:04 -0600
Subject: [PATCH 01/15] Starting dedicated pages for Email Rules
---
.../Controllers/UIStoresController.Email.cs | 151 +---------------
.../UIStoresController.EmailRules.cs | 169 ++++++++++++++++++
.../Views/UIStores/StoreEmailRulesList.cshtml | 60 +++++++
.../UIStores/StoreEmailRulesManage.cshtml | 160 +++++++++++++++++
.../Views/UIStores/StoreEmailSettings.cshtml | 2 +-
5 files changed, 396 insertions(+), 146 deletions(-)
create mode 100644 BTCPayServer/Controllers/UIStoresController.EmailRules.cs
create mode 100644 BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml
create mode 100644 BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
diff --git a/BTCPayServer/Controllers/UIStoresController.Email.cs b/BTCPayServer/Controllers/UIStoresController.Email.cs
index 27d649a5f..be2c67520 100644
--- a/BTCPayServer/Controllers/UIStoresController.Email.cs
+++ b/BTCPayServer/Controllers/UIStoresController.Email.cs
@@ -19,154 +19,15 @@ namespace BTCPayServer.Controllers;
public partial class UIStoresController
{
- [HttpGet("{storeId}/emails")]
- public async Task StoreEmails(string storeId)
- {
- var store = HttpContext.GetStoreData();
- if (store == null)
- return NotFound();
-
- var configured = await _emailSenderFactory.IsComplete(store.Id);
- if (!configured && !TempData.HasStatusMessage())
- {
- TempData.SetStatusMessageModel(new StatusMessageModel
- {
- Severity = StatusMessageModel.StatusSeverity.Warning,
- Html = "You need to configure email settings before this feature works." +
- $" Configure store email settings."
- });
- }
-
- var vm = new StoreEmailRuleViewModel { Rules = store.GetStoreBlob().EmailRules ?? [] };
- return View(vm);
- }
-
- [HttpPost("{storeId}/emails")]
- [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
- public async Task StoreEmails(string storeId, StoreEmailRuleViewModel vm, string command)
- {
- vm.Rules ??= [];
- int commandIndex = 0;
-
- var indSep = command.Split(':', StringSplitOptions.RemoveEmptyEntries);
- if (indSep.Length > 1)
- {
- commandIndex = int.Parse(indSep[1], CultureInfo.InvariantCulture);
- }
-
- if (command.StartsWith("remove", StringComparison.InvariantCultureIgnoreCase))
- {
- vm.Rules.RemoveAt(commandIndex);
- }
- else if (command == "add")
- {
- vm.Rules.Add(new StoreEmailRule());
-
- return View(vm);
- }
-
- for (var i = 0; i < vm.Rules.Count; i++)
- {
- var rule = vm.Rules[i];
-
- if (!string.IsNullOrEmpty(rule.To) && rule.To.Split(',', StringSplitOptions.RemoveEmptyEntries)
- .Any(s => !MailboxAddressValidator.TryParse(s, out _)))
- {
- ModelState.AddModelError($"{nameof(vm.Rules)}[{i}].{nameof(rule.To)}",
- StringLocalizer["Invalid mailbox address provided. Valid formats are: '{0}' or '{1}'", "test@example.com", "Firstname Lastname "]);
- }
- else if (!rule.CustomerEmail && string.IsNullOrEmpty(rule.To))
- ModelState.AddModelError($"{nameof(vm.Rules)}[{i}].{nameof(rule.To)}",
- StringLocalizer["Either recipient or \"Send the email to the buyer\" is required"]);
- }
-
- if (!ModelState.IsValid)
- {
- return View(vm);
- }
-
- var store = HttpContext.GetStoreData();
-
- if (store == null)
- return NotFound();
-
- string message = "";
-
- // update rules
- var blob = store.GetStoreBlob();
- blob.EmailRules = vm.Rules;
- if (store.SetStoreBlob(blob))
- {
- await _storeRepo.UpdateStore(store);
- message += StringLocalizer["Store email rules saved."] + " ";
- }
-
- if (command.StartsWith("test", StringComparison.InvariantCultureIgnoreCase))
- {
- try
- {
- var rule = vm.Rules[commandIndex];
- if (await _emailSenderFactory.IsComplete(store.Id))
- {
- var recipients = rule.To.Split(",", StringSplitOptions.RemoveEmptyEntries)
- .Select(o =>
- {
- MailboxAddressValidator.TryParse(o, out var mb);
- return mb;
- })
- .Where(o => o != null)
- .ToArray();
-
- var emailSender = await _emailSenderFactory.GetEmailSender(store.Id);
- emailSender.SendEmail(recipients.ToArray(), null, null, $"[TEST] {rule.Subject}", rule.Body);
- message += StringLocalizer["Test email sent — please verify you received it."];
- }
- else
- {
- message += StringLocalizer["Complete the email setup to send test emails."];
- }
- }
- catch (Exception ex)
- {
- TempData[WellKnownTempData.ErrorMessage] = message + StringLocalizer["Error sending test email: {0}", ex.Message].Value;
- return RedirectToAction("StoreEmails", new { storeId });
- }
- }
-
- if (!string.IsNullOrEmpty(message))
- {
- TempData.SetStatusMessageModel(new StatusMessageModel
- {
- Severity = StatusMessageModel.StatusSeverity.Success,
- Message = message
- });
- }
-
- return RedirectToAction("StoreEmails", new { storeId });
- }
-
- public class StoreEmailRuleViewModel
- {
- public List Rules { get; set; }
- }
-
- public class StoreEmailRule
- {
- [Required]
- public string Trigger { get; set; }
-
- public bool CustomerEmail { get; set; }
- public string To { get; set; }
-
- [Required]
- public string Subject { get; set; }
-
- [Required]
- public string Body { get; set; }
- }
+ // public class StoreEmailRuleViewModel
+ // {
+ // public List Rules { get; set; }
+ // }
+
+
[HttpGet("{storeId}/email-settings")]
public async Task StoreEmailSettings(string storeId)
{
diff --git a/BTCPayServer/Controllers/UIStoresController.EmailRules.cs b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs
new file mode 100644
index 000000000..aab9acf92
--- /dev/null
+++ b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.Linq;
+using System.Threading.Tasks;
+using BTCPayServer.Client;
+using BTCPayServer.Data;
+using BTCPayServer.Models;
+using BTCPayServer.Services.Mails;
+using BTCPayServer.Services.Stores;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace BTCPayServer.Controllers
+{
+ [Authorize(Policy = Policies.CanModifyStoreSettings)]
+ public partial class UIStoresController
+ {
+
+
+
+
+ [HttpGet("{storeId}/emails/rules")]
+ public IActionResult EmailRulesIndex(string storeId)
+ {
+ var store = HttpContext.GetStoreData();
+ if (store == null) return NotFound();
+
+ var rules = store.GetStoreBlob().EmailRules ?? new List();
+ return View("StoreEmailRulesList", rules);
+ }
+
+ [HttpGet("{storeId}/emails/rules/create")]
+ public IActionResult EmailRulesCreate(string storeId)
+ {
+ return View("StoreEmailRulesManage", new StoreEmailRuleViewModel { StoreId = storeId });
+ }
+
+ [HttpPost("{storeId}/emails/rules/create")]
+ public async Task EmailRulesCreate(string storeId, StoreEmailRuleViewModel model)
+ {
+ if (!ModelState.IsValid)
+ return View("StoreEmailRulesManage", model);
+
+ var store = await _storeRepo.FindStore(storeId);
+ if (store == null) return NotFound();
+
+ var blob = store.GetStoreBlob();
+ var rulesList = blob.EmailRules ?? new List();
+ rulesList.Add(new StoreEmailRule
+ {
+ Trigger = model.Trigger,
+ CustomerEmail = model.CustomerEmail,
+ To = model.To,
+ Subject = model.Subject,
+ Body = model.Body
+ });
+
+ blob.EmailRules = rulesList;
+ store.SetStoreBlob(blob);
+ await _storeRepo.UpdateStore(store);
+
+ return RedirectToAction(nameof(EmailRulesIndex), new { storeId });
+ }
+
+ [HttpGet("{storeId}/emails/rules/{ruleIndex}/edit")]
+ public IActionResult EmailRulesEdit(string storeId, int ruleIndex)
+ {
+ var store = HttpContext.GetStoreData();
+ if (store == null) return NotFound();
+
+ var rules = store.GetStoreBlob().EmailRules;
+ if (rules == null || ruleIndex >= rules.Count) return NotFound();
+
+ var rule = rules[ruleIndex];
+ return View("StoreEmailRulesManage", new StoreEmailRuleViewModel
+ {
+ StoreId = storeId,
+ Trigger = rule.Trigger,
+ CustomerEmail = rule.CustomerEmail,
+ To = rule.To,
+ Subject = rule.Subject,
+ Body = rule.Body
+ });
+ }
+
+ [HttpPost("{storeId}/emails/rules/{ruleIndex}/edit")]
+ public async Task EmailRulesEdit(string storeId, int ruleIndex, StoreEmailRuleViewModel model)
+ {
+ if (!ModelState.IsValid)
+ return View("StoreEmailRulesManage", model);
+
+ var store = await _storeRepo.FindStore(storeId);
+ if (store == null) return NotFound();
+
+ var blob = store.GetStoreBlob();
+ if (blob.EmailRules == null || ruleIndex >= blob.EmailRules.Count) return NotFound();
+
+ var rule = blob.EmailRules[ruleIndex];
+ rule.Trigger = model.Trigger;
+ rule.CustomerEmail = model.CustomerEmail;
+ rule.To = model.To;
+ rule.Subject = model.Subject;
+ rule.Body = model.Body;
+ store.SetStoreBlob(blob);
+ await _storeRepo.UpdateStore(store);
+
+ return RedirectToAction(nameof(EmailRulesIndex), new { storeId });
+ }
+
+ [HttpPost("{storeId}/emails/rules/{ruleIndex}/delete")]
+ public async Task EmailRulesDelete(string storeId, int ruleIndex)
+ {
+ var store = await _storeRepo.FindStore(storeId);
+ if (store == null) return NotFound();
+
+ var blob = store.GetStoreBlob();
+ if (blob.EmailRules == null || ruleIndex >= blob.EmailRules.Count) return NotFound();
+
+ blob.EmailRules.RemoveAt(ruleIndex);
+ store.SetStoreBlob(blob);
+ await _storeRepo.UpdateStore(store);
+
+ return RedirectToAction(nameof(Index), new { storeId });
+ }
+
+
+
+
+ public class StoreEmailRuleViewModel
+ {
+ public string StoreId { get; set; }
+
+ [Required]
+ public string Trigger { get; set; }
+
+ public bool CustomerEmail { get; set; }
+
+ public string To { get; set; }
+
+ [Required]
+ public string Subject { get; set; }
+
+ [Required]
+ public string Body { get; set; }
+ }
+
+
+
+ public class StoreEmailRule
+ {
+ [Required]
+ public string Trigger { get; set; }
+
+ public bool CustomerEmail { get; set; }
+
+
+ public string To { get; set; }
+
+ [Required]
+ public string Subject { get; set; }
+
+ [Required]
+ public string Body { get; set; }
+ }
+ }
+
+}
diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml
new file mode 100644
index 000000000..99b4c6fa5
--- /dev/null
+++ b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml
@@ -0,0 +1,60 @@
+@using BTCPayServer.Client
+@using BTCPayServer.TagHelpers
+@using Microsoft.AspNetCore.Mvc.TagHelpers
+@model List
+
+@{
+ var storeId = Context.GetStoreData().Id;
+ ViewData.SetActivePage(StoreNavPages.Emails, StringLocalizer["Email Rules"], storeId);
+}
+
+
+
+
+ Email rules allow BTCPay Server to send customized emails from your store based on events.
+
+
+
+
+
+
+ | Trigger |
+ Customer Email |
+ To |
+ Subject |
+ Actions |
+
+
+
+ @foreach (var rule in Model.Select((value, index) => new { value, index }))
+ {
+
+ | @rule.value.Trigger |
+ @(rule.value.CustomerEmail ? "Yes" : "No") |
+ @rule.value.To |
+ @rule.value.Subject |
+
+ Edit
+
+ |
+
+ }
+
+
+
diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
new file mode 100644
index 000000000..39e8ba1e6
--- /dev/null
+++ b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
@@ -0,0 +1,160 @@
+@using BTCPayServer.HostedServices.Webhooks
+@using Microsoft.AspNetCore.Mvc.TagHelpers
+@model BTCPayServer.Controllers.UIStoresController.StoreEmailRuleViewModel
+@inject WebhookSender WebhookSender
+
+@{
+ bool isEdit = Model.Trigger != null;
+ ViewData["Title"] = isEdit ? "Edit Store Email Rule" : "Create Store Email Rule";
+}
+
+@section PageHeadContent {
+
+}
+
+
+
+@section PageFootContent {
+
+
+
+}
diff --git a/BTCPayServer/Views/UIStores/StoreEmailSettings.cshtml b/BTCPayServer/Views/UIStores/StoreEmailSettings.cshtml
index cad31f50b..bebfef020 100644
--- a/BTCPayServer/Views/UIStores/StoreEmailSettings.cshtml
+++ b/BTCPayServer/Views/UIStores/StoreEmailSettings.cshtml
@@ -38,7 +38,7 @@
Email Rules
Email rules allow BTCPay Server to send customized emails from your store based on events.
-
+
Configure
From d434a2d4808f03b0b5409d10feb00a1d2e92fbbd Mon Sep 17 00:00:00 2001
From: rockstardev <5191402+rockstardev@users.noreply.github.com>
Date: Tue, 25 Feb 2025 01:50:51 -0600
Subject: [PATCH 02/15] Adding webhook registration for pending transcations
---
.../PendingTransactionService.cs | 87 ++++++++++++++++++-
BTCPayServer/Hosting/BTCPayServerServices.cs | 1 +
2 files changed, 87 insertions(+), 1 deletion(-)
diff --git a/BTCPayServer/HostedServices/PendingTransactionService.cs b/BTCPayServer/HostedServices/PendingTransactionService.cs
index 92bdfc3d1..f6a4e881c 100644
--- a/BTCPayServer/HostedServices/PendingTransactionService.cs
+++ b/BTCPayServer/HostedServices/PendingTransactionService.cs
@@ -1,14 +1,21 @@
#nullable enable
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using BTCPayServer.Client.Models;
+using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Events;
+using BTCPayServer.HostedServices.Webhooks;
using BTCPayServer.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using NBitcoin;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using WebhookDeliveryData = BTCPayServer.Data.WebhookDeliveryData;
namespace BTCPayServer.HostedServices;
@@ -19,7 +26,7 @@ public class PendingTransactionService(
EventAggregator eventAggregator,
ILogger logger,
ExplorerClientProvider explorerClientProvider)
- : EventHostedServiceBase(eventAggregator, logger), IPeriodicTask
+ : EventHostedServiceBase(eventAggregator, logger), IPeriodicTask, IWebhookProvider
{
protected override void SubscribeToEvents()
{
@@ -239,4 +246,82 @@ public class PendingTransactionService(
pt.State = PendingTransactionState.Broadcast;
await ctx.SaveChangesAsync();
}
+
+
+ public const string PendingTransactionCreated = "PendingTransactionCreated";
+ public const string PendingTransactionSigned = "PendingTransactionSigned";
+
+ public Dictionary GetSupportedWebhookTypes()
+ {
+ return new Dictionary
+ {
+ {PendingTransactionCreated, "Pending Transaction - Created"},
+ {PendingTransactionSigned, "Pending Transaction - Signed"}
+ };
+ }
+
+ public WebhookEvent CreateTestEvent(string type, params object[] args)
+ {
+ var storeId = args[0].ToString();
+ return new WebhookPendingTransactionEvent(type, storeId)
+ {
+ AppId = "__test__" + Guid.NewGuid() + "__test__",
+ SubscriptionId = "__test__" + Guid.NewGuid() + "__test__",
+ Status = "Active"
+ };
+ }
+
+ public class WebhookPendingTransactionEvent : StoreWebhookEvent
+ {
+ public WebhookPendingTransactionEvent(string type, string storeId)
+ {
+ if (!type.StartsWith("subscription", StringComparison.InvariantCultureIgnoreCase))
+ throw new ArgumentException("Invalid event type", nameof(type));
+ Type = type;
+ StoreId = storeId;
+ }
+
+
+ [JsonProperty(Order = 2)] public string AppId { get; set; }
+
+ [JsonProperty(Order = 3)] public string SubscriptionId { get; set; }
+ [JsonProperty(Order = 4)] public string Status { get; set; }
+ [JsonProperty(Order = 5)] public string PaymentRequestId { get; set; }
+ [JsonProperty(Order = 6)] public string Email { get; set; }
+ }
+
+ public class SubscriptionWebhookDeliveryRequest(
+ string receiptUrl,
+ string? webhookId,
+ WebhookPendingTransactionEvent webhookEvent,
+ WebhookDeliveryData? delivery,
+ WebhookBlob? webhookBlob,
+ BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings)
+ : WebhookSender.WebhookDeliveryRequest(webhookId!, webhookEvent, delivery!, webhookBlob!)
+ {
+ public override Task Interpolate(SendEmailRequest req,
+ UIStoresController.StoreEmailRule storeEmailRule)
+ {
+ if (storeEmailRule.CustomerEmail &&
+ MailboxAddressValidator.TryParse(webhookEvent.Email, out var bmb))
+ {
+ req.Email ??= string.Empty;
+ req.Email += $",{bmb}";
+ }
+
+ req.Subject = Interpolate(req.Subject);
+ req.Body = Interpolate(req.Body);
+ return Task.FromResult(req)!;
+ }
+
+ private string Interpolate(string str)
+ {
+ var res = str.Replace("{Subscription.SubscriptionId}", webhookEvent.SubscriptionId)
+ .Replace("{Subscription.Status}", webhookEvent.Status)
+ .Replace("{Subscription.PaymentRequestId}", webhookEvent.PaymentRequestId)
+ .Replace("{Subscription.AppId}", webhookEvent.AppId);
+
+ return res;
+ }
+ }
}
diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs
index 337bf0367..4a0a89a8c 100644
--- a/BTCPayServer/Hosting/BTCPayServerServices.cs
+++ b/BTCPayServer/Hosting/BTCPayServerServices.cs
@@ -354,6 +354,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton();
services.TryAddSingleton();
services.AddSingleton();
+ services.AddSingleton(o => o.GetRequiredService());
services.AddScheduledTask(TimeSpan.FromMinutes(10));
services.TryAddSingleton();
services.AddSingleton(provider => provider.GetService());
From 75dee3170f0a4f32ed9b34b9402695a2fd8ad88a Mon Sep 17 00:00:00 2001
From: rockstardev <5191402+rockstardev@users.noreply.github.com>
Date: Sat, 15 Mar 2025 02:47:18 -0500
Subject: [PATCH 03/15] Simplifying javascript now that there aren't multiple
forms on page
---
.../UIStores/StoreEmailRulesManage.cshtml | 57 ++++++++++++-------
1 file changed, 35 insertions(+), 22 deletions(-)
diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
index 39e8ba1e6..6401f3a88 100644
--- a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
+++ b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
@@ -24,7 +24,8 @@
@@ -101,7 +102,7 @@
+
}
From 9cf25c7c9efc61f1f00454a81a19e2d70923ea83 Mon Sep 17 00:00:00 2001
From: rockstardev <5191402+rockstardev@users.noreply.github.com>
Date: Sat, 15 Mar 2025 03:10:01 -0500
Subject: [PATCH 04/15] Simplifying code
---
.../UIStoresController.EmailRules.cs | 46 ++-----------------
.../Views/UIStores/StoreEmailRulesList.cshtml | 7 +--
.../UIStores/StoreEmailRulesManage.cshtml | 7 +--
3 files changed, 13 insertions(+), 47 deletions(-)
diff --git a/BTCPayServer/Controllers/UIStoresController.EmailRules.cs b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs
index aab9acf92..9aabb999f 100644
--- a/BTCPayServer/Controllers/UIStoresController.EmailRules.cs
+++ b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs
@@ -17,10 +17,6 @@ namespace BTCPayServer.Controllers
[Authorize(Policy = Policies.CanModifyStoreSettings)]
public partial class UIStoresController
{
-
-
-
-
[HttpGet("{storeId}/emails/rules")]
public IActionResult EmailRulesIndex(string storeId)
{
@@ -34,11 +30,11 @@ namespace BTCPayServer.Controllers
[HttpGet("{storeId}/emails/rules/create")]
public IActionResult EmailRulesCreate(string storeId)
{
- return View("StoreEmailRulesManage", new StoreEmailRuleViewModel { StoreId = storeId });
+ return View("StoreEmailRulesManage", new StoreEmailRule());
}
[HttpPost("{storeId}/emails/rules/create")]
- public async Task EmailRulesCreate(string storeId, StoreEmailRuleViewModel model)
+ public async Task EmailRulesCreate(string storeId, StoreEmailRule model)
{
if (!ModelState.IsValid)
return View("StoreEmailRulesManage", model);
@@ -74,19 +70,11 @@ namespace BTCPayServer.Controllers
if (rules == null || ruleIndex >= rules.Count) return NotFound();
var rule = rules[ruleIndex];
- return View("StoreEmailRulesManage", new StoreEmailRuleViewModel
- {
- StoreId = storeId,
- Trigger = rule.Trigger,
- CustomerEmail = rule.CustomerEmail,
- To = rule.To,
- Subject = rule.Subject,
- Body = rule.Body
- });
+ return View("StoreEmailRulesManage", rule);
}
[HttpPost("{storeId}/emails/rules/{ruleIndex}/edit")]
- public async Task EmailRulesEdit(string storeId, int ruleIndex, StoreEmailRuleViewModel model)
+ public async Task EmailRulesEdit(string storeId, int ruleIndex, StoreEmailRule model)
{
if (!ModelState.IsValid)
return View("StoreEmailRulesManage", model);
@@ -122,31 +110,8 @@ namespace BTCPayServer.Controllers
store.SetStoreBlob(blob);
await _storeRepo.UpdateStore(store);
- return RedirectToAction(nameof(Index), new { storeId });
+ return RedirectToAction(nameof(EmailRulesIndex), new { storeId });
}
-
-
-
-
- public class StoreEmailRuleViewModel
- {
- public string StoreId { get; set; }
-
- [Required]
- public string Trigger { get; set; }
-
- public bool CustomerEmail { get; set; }
-
- public string To { get; set; }
-
- [Required]
- public string Subject { get; set; }
-
- [Required]
- public string Body { get; set; }
- }
-
-
public class StoreEmailRule
{
@@ -155,7 +120,6 @@ namespace BTCPayServer.Controllers
public bool CustomerEmail { get; set; }
-
public string To { get; set; }
[Required]
diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml
index 99b4c6fa5..249aaa4b0 100644
--- a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml
+++ b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml
@@ -48,9 +48,10 @@
@rule.value.To |
@rule.value.Subject |
- Edit
-
|
diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
index 6401f3a88..f30212f78 100644
--- a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
+++ b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
@@ -1,9 +1,10 @@
@using BTCPayServer.HostedServices.Webhooks
@using Microsoft.AspNetCore.Mvc.TagHelpers
-@model BTCPayServer.Controllers.UIStoresController.StoreEmailRuleViewModel
+@model BTCPayServer.Controllers.UIStoresController.StoreEmailRule
@inject WebhookSender WebhookSender
@{
+ var storeId = Context.GetStoreData().Id;
bool isEdit = Model.Trigger != null;
ViewData["Title"] = isEdit ? "Edit Store Email Rule" : "Create Store Email Rule";
}
@@ -12,12 +13,12 @@
}
-
-
-
-
-
- | Trigger |
- Customer Email |
- To |
- Subject |
- Actions |
-
-
-
- @foreach (var rule in Model.Select((value, index) => new { value, index }))
- {
-
- | @rule.value.Trigger |
- @(rule.value.CustomerEmail ? "Yes" : "No") |
- @rule.value.To |
- @rule.value.Subject |
-
- Edit
- -
-
- |
-
- }
-
-
-
+@if (Model.Any())
+{
+
+
+
+
+ | Trigger |
+ Customer Email |
+ To |
+ Subject |
+ Actions |
+
+
+
+ @foreach (var rule in Model.Select((value, index) => new { value, index }))
+ {
+
+ | @rule.value.Trigger |
+ @(rule.value.CustomerEmail ? "Yes" : "No") |
+ @rule.value.To |
+ @rule.value.Subject |
+
+ Edit
+ -
+
+ |
+
+ }
+
+
+
+}
+else
+{
+
+ There are no rules yet.
+
+}
diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
index f30212f78..5126378ab 100644
--- a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
+++ b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
@@ -17,7 +17,7 @@
From 629087a0546a8eb4ddadb9aac805baba1a7f387c Mon Sep 17 00:00:00 2001
From: rockstardev <5191402+rockstardev@users.noreply.github.com>
Date: Sat, 15 Mar 2025 03:56:16 -0500
Subject: [PATCH 06/15] Unifying the format of WebhookEventTypes display text
---
.../Webhooks/InvoiceWebhookProvider.cs | 14 +++++++-------
.../Webhooks/PaymentRequestWebhookProvider.cs | 8 ++++----
.../Webhooks/PayoutWebhookProvider.cs | 6 +++---
3 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/BTCPayServer/HostedServices/Webhooks/InvoiceWebhookProvider.cs b/BTCPayServer/HostedServices/Webhooks/InvoiceWebhookProvider.cs
index 25d87f79b..7be8a8546 100644
--- a/BTCPayServer/HostedServices/Webhooks/InvoiceWebhookProvider.cs
+++ b/BTCPayServer/HostedServices/Webhooks/InvoiceWebhookProvider.cs
@@ -22,13 +22,13 @@ public class InvoiceWebhookProvider : WebhookProvider
{
return new Dictionary
{
- {WebhookEventType.InvoiceCreated, "A new invoice has been created"},
- {WebhookEventType.InvoiceReceivedPayment, "A new payment has been received"},
- {WebhookEventType.InvoicePaymentSettled, "A payment has been settled"},
- {WebhookEventType.InvoiceProcessing, "An invoice is processing"},
- {WebhookEventType.InvoiceExpired, "An invoice has expired"},
- {WebhookEventType.InvoiceSettled, "An invoice has been settled"},
- {WebhookEventType.InvoiceInvalid, "An invoice became invalid"},
+ {WebhookEventType.InvoiceCreated, "Invoice - Created"},
+ {WebhookEventType.InvoiceReceivedPayment, "Invoice - Received Payment"},
+ {WebhookEventType.InvoicePaymentSettled, "Invoice - Payment Settled"},
+ {WebhookEventType.InvoiceProcessing, "Invoice - Is Processing"},
+ {WebhookEventType.InvoiceExpired, "Invoice - Expired"},
+ {WebhookEventType.InvoiceSettled, "Invoice - Is Settled"},
+ {WebhookEventType.InvoiceInvalid, "Invoice - Became Invalid"},
};
}
diff --git a/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookProvider.cs b/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookProvider.cs
index d6fb48bcd..c018b3ea4 100644
--- a/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookProvider.cs
+++ b/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookProvider.cs
@@ -18,10 +18,10 @@ public class PaymentRequestWebhookProvider: WebhookProvider
{
return new Dictionary()
{
- {WebhookEventType.PaymentRequestCreated, "Payment Request Created"},
- {WebhookEventType.PaymentRequestUpdated, "Payment Request Updated"},
- {WebhookEventType.PaymentRequestArchived, "Payment Request Archived"},
- {WebhookEventType.PaymentRequestStatusChanged, "Payment Request Status Changed"},
+ {WebhookEventType.PaymentRequestCreated, "Payment Request - Created"},
+ {WebhookEventType.PaymentRequestUpdated, "Payment Request - Updated"},
+ {WebhookEventType.PaymentRequestArchived, "Payment Request - Archived"},
+ {WebhookEventType.PaymentRequestStatusChanged, "Payment Request - Status Changed"},
};
}
diff --git a/BTCPayServer/HostedServices/Webhooks/PayoutWebhookProvider.cs b/BTCPayServer/HostedServices/Webhooks/PayoutWebhookProvider.cs
index 4a37064f0..28874fafd 100644
--- a/BTCPayServer/HostedServices/Webhooks/PayoutWebhookProvider.cs
+++ b/BTCPayServer/HostedServices/Webhooks/PayoutWebhookProvider.cs
@@ -36,9 +36,9 @@ public class PayoutWebhookProvider(EventAggregator eventAggregator, ILogger()
{
- {WebhookEventType.PayoutCreated, "A payout has been created"},
- {WebhookEventType.PayoutApproved, "A payout has been approved"},
- {WebhookEventType.PayoutUpdated, "A payout was updated"}
+ {WebhookEventType.PayoutCreated, "Payout - Created"},
+ {WebhookEventType.PayoutApproved, "Payout - Approved"},
+ {WebhookEventType.PayoutUpdated, "Payout - Updated"}
};
}
From d800cab2d16a08bb516b7b27cdbcb081d7bd2d84 Mon Sep 17 00:00:00 2001
From: rockstardev <5191402+rockstardev@users.noreply.github.com>
Date: Sat, 15 Mar 2025 03:56:49 -0500
Subject: [PATCH 07/15] Sorting Webhook text so it is ordered for user
selection
---
BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
index 5126378ab..12d8fc5bd 100644
--- a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
+++ b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml
@@ -25,7 +25,8 @@