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. +

+ +
+ + + + + + + + + + + + @foreach (var rule in Model.Select((value, index) => new { value, index })) + { + + + + + + + + } + +
TriggerCustomer EmailToSubjectActions
@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 { + +} + +
+ + + +
+ + + +
Choose what event sends the email.
+
+ +
+ + + +
Who to send the email to. For multiple emails, separate with a comma.
+
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ + + + + + + + + + + + + + + +
Placeholders
Invoice + {Invoice.Id}, + {Invoice.StoreId}, + {Invoice.Price}, + {Invoice.Currency}, + {Invoice.Status}, + {Invoice.AdditionalStatus}, + {Invoice.OrderId} + {Invoice.Metadata}* +
Request + {PaymentRequest.Id}, + {PaymentRequest.Price}, + {PaymentRequest.Currency}, + {PaymentRequest.Title}, + {PaymentRequest.Description}, + {PaymentRequest.Status} + {PaymentRequest.FormResponse}* +
Payout + {Payout.Id}, + {Payout.PullPaymentId}, + {Payout.Destination}, + {Payout.State} + {Payout.Metadata}* +
* These fields are JSON objects. You can access properties within them using this syntax. One example is {Invoice.Metadata.itemCode}
+
+
+ + + +@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 @@
- +
Choose what event sends the email.
@@ -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 -
- + Edit + - + + Delete
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 @@ } -
+ From 5ce8648bd2a9956832479c985da0fc5ca71b7f97 Mon Sep 17 00:00:00 2001 From: rockstardev <5191402+rockstardev@users.noreply.github.com> Date: Sat, 15 Mar 2025 03:24:19 -0500 Subject: [PATCH 05/15] Fixing tests --- BTCPayServer.Tests/SeleniumTests.cs | 12 +-- .../UIStoresController.EmailRules.cs | 15 +++- .../Views/UIStores/StoreEmailRulesList.cshtml | 73 +++++++++++-------- .../UIStores/StoreEmailRulesManage.cshtml | 2 +- 4 files changed, 63 insertions(+), 39 deletions(-) diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 6bec76f44..f1ebb2f1a 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -828,14 +828,16 @@ namespace BTCPayServer.Tests Assert.DoesNotContain("You need to configure email settings before this feature works", s.Driver.PageSource); s.Driver.FindElement(By.Id("CreateEmailRule")).Click(); - var select = new SelectElement(s.Driver.FindElement(By.Id("Rules_0__Trigger"))); + var select = new SelectElement(s.Driver.FindElement(By.Id("Trigger"))); select.SelectByText("An invoice has been settled", true); - s.Driver.FindElement(By.Id("Rules_0__To")).SendKeys("test@gmail.com"); - s.Driver.FindElement(By.Id("Rules_0__CustomerEmail")).Click(); - s.Driver.FindElement(By.Id("Rules_0__Subject")).SendKeys("Thanks!"); + s.Driver.FindElement(By.Id("To")).SendKeys("test@gmail.com"); + s.Driver.FindElement(By.Id("CustomerEmail")).Click(); + s.Driver.FindElement(By.Id("Subject")).SendKeys("Thanks!"); s.Driver.FindElement(By.ClassName("note-editable")).SendKeys("Your invoice is settled"); s.Driver.FindElement(By.Id("SaveEmailRules")).Click(); - Assert.Contains("Store email rules saved", s.FindAlertMessage().Text); + // we now have a rule + Assert.DoesNotContain("There are no rules yet.", s.Driver.PageSource); + Assert.Contains("test@gmail.com", s.Driver.PageSource); s.GoToStore(StoreNavPages.Emails); Assert.True(s.Driver.FindElement(By.Id("IsCustomSMTP")).Selected); diff --git a/BTCPayServer/Controllers/UIStoresController.EmailRules.cs b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs index 9aabb999f..369d67e5f 100644 --- a/BTCPayServer/Controllers/UIStoresController.EmailRules.cs +++ b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs @@ -4,6 +4,8 @@ using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using System.Threading.Tasks; +using BTCPayServer.Abstractions.Extensions; +using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Models; @@ -18,10 +20,21 @@ namespace BTCPayServer.Controllers public partial class UIStoresController { [HttpGet("{storeId}/emails/rules")] - public IActionResult EmailRulesIndex(string storeId) + public async Task EmailRulesIndex(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 rules = store.GetStoreBlob().EmailRules ?? new List(); return View("StoreEmailRulesList", rules); diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml index 249aaa4b0..250e64c67 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml @@ -17,7 +17,7 @@

@ViewData["Title"]

- Create Email Rule @@ -28,34 +28,43 @@ Email rules allow BTCPay Server to send customized emails from your store based on events.

-
- - - - - - - - - - - - @foreach (var rule in Model.Select((value, index) => new { value, index })) - { - - - - - - - - } - -
TriggerCustomer EmailToSubjectActions
@rule.value.Trigger@(rule.value.CustomerEmail ? "Yes" : "No")@rule.value.To@rule.value.Subject - Edit - - - - Delete - -
-
+@if (Model.Any()) +{ +
+ + + + + + + + + + + + @foreach (var rule in Model.Select((value, index) => new { value, index })) + { + + + + + + + + } + +
TriggerCustomer EmailToSubjectActions
@rule.value.Trigger@(rule.value.CustomerEmail ? "Yes" : "No")@rule.value.To@rule.value.Subject + Edit + - +
+ Delete +
+
+
+} +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 @@
-
Choose what event sends the email.
From 5a24651be04673249d861ca206b74e6584592c14 Mon Sep 17 00:00:00 2001 From: rockstardev <5191402+rockstardev@users.noreply.github.com> Date: Sat, 15 Mar 2025 03:57:00 -0500 Subject: [PATCH 08/15] CanSeetupEmailRules test --- BTCPayServer.Tests/SeleniumTests.cs | 77 ++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index f1ebb2f1a..491fb5624 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -829,7 +829,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("CreateEmailRule")).Click(); var select = new SelectElement(s.Driver.FindElement(By.Id("Trigger"))); - select.SelectByText("An invoice has been settled", true); + select.SelectByValue("InvoicePaymentSettled"); s.Driver.FindElement(By.Id("To")).SendKeys("test@gmail.com"); s.Driver.FindElement(By.Id("CustomerEmail")).Click(); s.Driver.FindElement(By.Id("Subject")).SendKeys("Thanks!"); @@ -843,6 +843,81 @@ namespace BTCPayServer.Tests Assert.True(s.Driver.FindElement(By.Id("IsCustomSMTP")).Selected); } + [Fact(Timeout = TestTimeout)] + public async Task CanSetupEmailRules() + { + using var s = CreateSeleniumTester(); + await s.StartAsync(); + s.RegisterNewUser(true); + s.CreateNewStore(); + + // Store Email Rules + s.GoToStore(StoreNavPages.Emails); + s.Driver.FindElement(By.Id("ConfigureEmailRules")).Click(); + Assert.Contains("There are no rules yet.", s.Driver.PageSource); + // by default selenium tests setup email server settings + Assert.DoesNotContain("You need to configure email settings before this feature works", s.Driver.PageSource); + + // invoice created rule + s.Driver.FindElement(By.Id("CreateEmailRule")).Click(); + var select = new SelectElement(s.Driver.FindElement(By.Id("Trigger"))); + select.SelectByValue("InvoiceCreated"); + s.Driver.FindElement(By.Id("To")).SendKeys("invoicecreated@gmail.com"); + s.Driver.FindElement(By.Id("CustomerEmail")).Click(); + s.Driver.FindElement(By.Id("SaveEmailRules")).Click(); + + // Ensure that the rule is created + Assert.DoesNotContain("There are no rules yet.", s.Driver.PageSource); + Assert.Contains("invoicecreated@gmail.com", s.Driver.PageSource); + Assert.Contains("Invoice {Invoice.Id} created", s.Driver.PageSource); + Assert.Contains("Yes", s.Driver.PageSource); + + // payment request status changed rule + s.Driver.FindElement(By.Id("CreateEmailRule")).Click(); + select = new SelectElement(s.Driver.FindElement(By.Id("Trigger"))); + select.SelectByValue("PaymentRequestStatusChanged"); + s.Driver.FindElement(By.Id("To")).SendKeys("statuschanged@gmail.com"); + s.Driver.FindElement(By.Id("Subject")).SendKeys("Status changed!"); + s.Driver.FindElement(By.ClassName("note-editable")).SendKeys("Your Payment Request Status is Changed"); + s.Driver.FindElement(By.Id("SaveEmailRules")).Click(); + + // Validate the second rule is added + Assert.Contains("statuschanged@gmail.com", s.Driver.PageSource); + Assert.Contains("Status changed!", s.Driver.PageSource); + + // Select the second rule’s edit button + var editButtons = s.Driver.FindElements(By.XPath("//a[contains(text(), 'Edit')]")); + Assert.True(editButtons.Count >= 2, "Expected at least two edit buttons but found fewer."); + + editButtons[1].Click(); // Clicks the second Edit button + + // Modify the second rule from statuschanged@gmail.com to changedagain@gmail.com + var toField = s.Driver.FindElement(By.Id("To")); + toField.Clear(); + toField.SendKeys("changedagain@gmail.com"); + s.Driver.FindElement(By.Id("SaveEmailRules")).Click(); + + // Validate that the email is updated in the list of email rules + Assert.Contains("changedagain@gmail.com", s.Driver.PageSource); + Assert.DoesNotContain("statuschanged@gmail.com", s.Driver.PageSource); + + // Delete both email rules + var deleteLinks = s.Driver.FindElements(By.XPath("//a[contains(text(), 'Delete')]")); + Assert.True(deleteLinks.Count == 2, "Expected exactly two delete buttons but found a different number."); + + deleteLinks[0].Click(); + s.Driver.SwitchTo().Alert().Accept(); // Confirm the delete + + deleteLinks = s.Driver.FindElements(By.XPath("//a[contains(text(), 'Delete')]")); // Refresh list + Assert.True(deleteLinks.Count == 1, "Expected one delete button remaining."); + + deleteLinks[0].Click(); + s.Driver.SwitchTo().Alert().Accept(); // Confirm the second delete + + // Validate that there are no more rules + Assert.Contains("There are no rules yet.", s.Driver.PageSource); + } + [Fact(Timeout = TestTimeout)] public async Task CanUseDynamicDns() { From 1b2dfb3e82919454818ef500049c768bc52e21cc Mon Sep 17 00:00:00 2001 From: rockstardev <5191402+rockstardev@users.noreply.github.com> Date: Sat, 15 Mar 2025 04:10:35 -0500 Subject: [PATCH 09/15] Removing legacy class --- .../Controllers/UIStoresController.Email.cs | 9 - .../Views/UIStores/StoreEmails.cshtml | 211 ------------------ 2 files changed, 220 deletions(-) delete mode 100644 BTCPayServer/Views/UIStores/StoreEmails.cshtml diff --git a/BTCPayServer/Controllers/UIStoresController.Email.cs b/BTCPayServer/Controllers/UIStoresController.Email.cs index be2c67520..75ad21ceb 100644 --- a/BTCPayServer/Controllers/UIStoresController.Email.cs +++ b/BTCPayServer/Controllers/UIStoresController.Email.cs @@ -19,15 +19,6 @@ namespace BTCPayServer.Controllers; public partial class UIStoresController { - - - // public class StoreEmailRuleViewModel - // { - // public List Rules { get; set; } - // } - - - [HttpGet("{storeId}/email-settings")] public async Task StoreEmailSettings(string storeId) { diff --git a/BTCPayServer/Views/UIStores/StoreEmails.cshtml b/BTCPayServer/Views/UIStores/StoreEmails.cshtml deleted file mode 100644 index 99e96577c..000000000 --- a/BTCPayServer/Views/UIStores/StoreEmails.cshtml +++ /dev/null @@ -1,211 +0,0 @@ -@using BTCPayServer.HostedServices.Webhooks -@using Microsoft.AspNetCore.Mvc.TagHelpers -@using BTCPayServer.Client -@using BTCPayServer.Abstractions.TagHelpers -@model BTCPayServer.Controllers.UIStoresController.StoreEmailRuleViewModel -@inject WebhookSender WebhookSender - -@{ - var storeId = Context.GetStoreData().Id; - ViewData.SetActivePage(StoreNavPages.Emails, StringLocalizer["Email Rules"], storeId); -} - -@section PageHeadContent { - -} - -
- - -

- Email rules allow BTCPay Server to send customized emails from your store based on events. -

-
-
- @if (!ViewContext.ModelState.IsValid) - { -
- } - @if (Model.Rules.Any()) - { -
    - @for (var index = 0; index < Model.Rules.Count; index++) - { - - } -
- } - else - { -

- There are no rules yet. -

- } -
-
- - -@section PageFootContent { - - - -} From 0997e9b76c294a78ab5b79a9023426f2da28879b Mon Sep 17 00:00:00 2001 From: rockstardev <5191402+rockstardev@users.noreply.github.com> Date: Sat, 15 Mar 2025 04:40:59 -0500 Subject: [PATCH 10/15] Fixing Selenium tests --- BTCPayServer.Tests/SeleniumTests.cs | 13 +++++++------ BTCPayServer/Services/Translations.Default.cs | 2 ++ .../Views/UIStores/StoreEmailRulesList.cshtml | 2 +- .../Views/UIStores/StoreEmailRulesManage.cshtml | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 491fb5624..fdb4c9155 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -855,8 +855,7 @@ namespace BTCPayServer.Tests s.GoToStore(StoreNavPages.Emails); s.Driver.FindElement(By.Id("ConfigureEmailRules")).Click(); Assert.Contains("There are no rules yet.", s.Driver.PageSource); - // by default selenium tests setup email server settings - Assert.DoesNotContain("You need to configure email settings before this feature works", s.Driver.PageSource); + Assert.Contains("You need to configure email settings before this feature works", s.Driver.PageSource); // invoice created rule s.Driver.FindElement(By.Id("CreateEmailRule")).Click(); @@ -906,13 +905,11 @@ namespace BTCPayServer.Tests Assert.True(deleteLinks.Count == 2, "Expected exactly two delete buttons but found a different number."); deleteLinks[0].Click(); - s.Driver.SwitchTo().Alert().Accept(); // Confirm the delete deleteLinks = s.Driver.FindElements(By.XPath("//a[contains(text(), 'Delete')]")); // Refresh list Assert.True(deleteLinks.Count == 1, "Expected one delete button remaining."); deleteLinks[0].Click(); - s.Driver.SwitchTo().Alert().Accept(); // Confirm the second delete // Validate that there are no more rules Assert.Contains("There are no rules yet.", s.Driver.PageSource); @@ -3960,7 +3957,6 @@ retry: s.Server.ActivateLightning(); await s.StartAsync(); await s.Server.EnsureChannelsSetup(); - var storeSettingsPaths = new [] {"settings", "rates", "checkout", "tokens", "users", "roles", "webhooks", "payout-processors", "payout-processors/onchain-automated/BTC", "payout-processors/lightning-automated/BTC", "emails", "email-settings", "forms"}; // Setup user, store and wallets s.RegisterNewUser(); @@ -3993,6 +3989,10 @@ retry: s.AssertPageAccess(false, GetStorePath("lightning/BTC")); s.AssertPageAccess(false, GetStorePath("lightning/BTC/settings")); s.AssertPageAccess(false, GetStorePath("apps/create")); + + var storeSettingsPaths = new [] {"settings", "rates", "checkout", "tokens", "users", "roles", "webhooks", + "payout-processors", "payout-processors/onchain-automated/BTC", "payout-processors/lightning-automated/BTC", + "emails/rules", "email-settings", "forms"}; foreach (var path in storeSettingsPaths) { // should have view access to settings, but no submit buttons or create links TestLogs.LogInformation($"Checking access to store page {path} as admin"); @@ -4013,7 +4013,8 @@ retry: s.Server.ActivateLightning(); await s.StartAsync(); await s.Server.EnsureChannelsSetup(); - var storeSettingsPaths = new [] {"settings", "rates", "checkout", "tokens", "users", "roles", "webhooks", "payout-processors", "payout-processors/onchain-automated/BTC", "payout-processors/lightning-automated/BTC", "emails", "email-settings", "forms"}; + var storeSettingsPaths = new [] {"settings", "rates", "checkout", "tokens", "users", "roles", "webhooks", "payout-processors", + "payout-processors/onchain-automated/BTC", "payout-processors/lightning-automated/BTC", "emails/rules", "email-settings", "forms"}; // Setup users var manager = s.RegisterNewUser(); diff --git a/BTCPayServer/Services/Translations.Default.cs b/BTCPayServer/Services/Translations.Default.cs index 77d434cff..47a536c28 100644 --- a/BTCPayServer/Services/Translations.Default.cs +++ b/BTCPayServer/Services/Translations.Default.cs @@ -523,6 +523,8 @@ namespace BTCPayServer.Services "Email password reset functionality is not configured for this server. Please contact the server administrator to assist with account recovery.": "", "email rules": "", "Email Rules": "", + "Create Email Rule": "", + "Edit Email Rule": "", "Email rules allow BTCPay Server to send customized emails from your store based on events.": "", "Email sent to {0}. Please verify you received it.": "", "Email Server": "", diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml index 250e64c67..9c5d2750b 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml @@ -53,7 +53,7 @@ Edit -
- Delete + Delete
diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml index 12d8fc5bd..a2fed01ed 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml @@ -6,7 +6,7 @@ @{ var storeId = Context.GetStoreData().Id; bool isEdit = Model.Trigger != null; - ViewData["Title"] = isEdit ? "Edit Store Email Rule" : "Create Store Email Rule"; + ViewData.SetActivePage(StoreNavPages.Emails, StringLocalizer[isEdit ? "Edit Email Rule" : "Create Email Rule"], storeId); } @section PageHeadContent { From 0d49594fe217bafe10e0d3590561a32d4389e51f Mon Sep 17 00:00:00 2001 From: rockstardev <5191402+rockstardev@users.noreply.github.com> Date: Wed, 19 Mar 2025 17:01:52 -0500 Subject: [PATCH 11/15] Fixing issue with Selenium tests --- BTCPayServer/Controllers/UIStoresController.EmailRules.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/BTCPayServer/Controllers/UIStoresController.EmailRules.cs b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs index 369d67e5f..7a00932a2 100644 --- a/BTCPayServer/Controllers/UIStoresController.EmailRules.cs +++ b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs @@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Mvc; namespace BTCPayServer.Controllers { - [Authorize(Policy = Policies.CanModifyStoreSettings)] public partial class UIStoresController { [HttpGet("{storeId}/emails/rules")] From 55cdd63c448faf383e591cde9593ab283c1d1151 Mon Sep 17 00:00:00 2001 From: rockstardev <5191402+rockstardev@users.noreply.github.com> Date: Wed, 19 Mar 2025 17:02:03 -0500 Subject: [PATCH 12/15] Renaming methods to align with views --- .../UIStoresController.EmailRules.cs | 24 ++++++++++++------- .../Views/UIStores/StoreEmailRulesList.cshtml | 6 ++--- .../UIStores/StoreEmailRulesManage.cshtml | 4 ++-- .../Views/UIStores/StoreEmailSettings.cshtml | 3 ++- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/BTCPayServer/Controllers/UIStoresController.EmailRules.cs b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs index 7a00932a2..5a66d0b22 100644 --- a/BTCPayServer/Controllers/UIStoresController.EmailRules.cs +++ b/BTCPayServer/Controllers/UIStoresController.EmailRules.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using System.Threading.Tasks; +using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; @@ -19,7 +20,7 @@ namespace BTCPayServer.Controllers public partial class UIStoresController { [HttpGet("{storeId}/emails/rules")] - public async Task EmailRulesIndex(string storeId) + public async Task StoreEmailRulesList(string storeId) { var store = HttpContext.GetStoreData(); if (store == null) return NotFound(); @@ -40,13 +41,15 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/emails/rules/create")] - public IActionResult EmailRulesCreate(string storeId) + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + public IActionResult StoreEmailRulesCreate(string storeId) { return View("StoreEmailRulesManage", new StoreEmailRule()); } [HttpPost("{storeId}/emails/rules/create")] - public async Task EmailRulesCreate(string storeId, StoreEmailRule model) + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + public async Task StoreEmailRulesCreate(string storeId, StoreEmailRule model) { if (!ModelState.IsValid) return View("StoreEmailRulesManage", model); @@ -69,11 +72,12 @@ namespace BTCPayServer.Controllers store.SetStoreBlob(blob); await _storeRepo.UpdateStore(store); - return RedirectToAction(nameof(EmailRulesIndex), new { storeId }); + return RedirectToAction(nameof(StoreEmailRulesList), new { storeId }); } [HttpGet("{storeId}/emails/rules/{ruleIndex}/edit")] - public IActionResult EmailRulesEdit(string storeId, int ruleIndex) + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + public IActionResult StoreEmailRulesEdit(string storeId, int ruleIndex) { var store = HttpContext.GetStoreData(); if (store == null) return NotFound(); @@ -86,7 +90,8 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/emails/rules/{ruleIndex}/edit")] - public async Task EmailRulesEdit(string storeId, int ruleIndex, StoreEmailRule model) + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + public async Task StoreEmailRulesEdit(string storeId, int ruleIndex, StoreEmailRule model) { if (!ModelState.IsValid) return View("StoreEmailRulesManage", model); @@ -106,11 +111,12 @@ namespace BTCPayServer.Controllers store.SetStoreBlob(blob); await _storeRepo.UpdateStore(store); - return RedirectToAction(nameof(EmailRulesIndex), new { storeId }); + return RedirectToAction(nameof(StoreEmailRulesList), new { storeId }); } [HttpPost("{storeId}/emails/rules/{ruleIndex}/delete")] - public async Task EmailRulesDelete(string storeId, int ruleIndex) + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + public async Task StoreEmailRulesDelete(string storeId, int ruleIndex) { var store = await _storeRepo.FindStore(storeId); if (store == null) return NotFound(); @@ -122,7 +128,7 @@ namespace BTCPayServer.Controllers store.SetStoreBlob(blob); await _storeRepo.UpdateStore(store); - return RedirectToAction(nameof(EmailRulesIndex), new { storeId }); + return RedirectToAction(nameof(StoreEmailRulesList), new { storeId }); } public class StoreEmailRule diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml index 9c5d2750b..24c580dde 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml @@ -17,7 +17,7 @@

@ViewData["Title"]

- Create Email Rule @@ -50,9 +50,9 @@ @rule.value.To @rule.value.Subject - Edit + Edit - -
+ Delete
diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml index a2fed01ed..4863fad01 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml @@ -13,12 +13,12 @@ } -
+ diff --git a/BTCPayServer/Views/UIStores/StoreEmailSettings.cshtml b/BTCPayServer/Views/UIStores/StoreEmailSettings.cshtml index bebfef020..e228c8503 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailSettings.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailSettings.cshtml @@ -38,7 +38,8 @@

Email Rules

Email rules allow BTCPay Server to send customized emails from your store based on events.

- + Configure
From 8aa6ac289d47806d029a0e86957badcb000728ec Mon Sep 17 00:00:00 2001 From: rockstardev <5191402+rockstardev@users.noreply.github.com> Date: Wed, 19 Mar 2025 17:19:25 -0500 Subject: [PATCH 13/15] Fixing the problem with permission on CreateEmailRule button --- BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml index 24c580dde..e949b7fbf 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesList.cshtml @@ -17,7 +17,7 @@

@ViewData["Title"]

- Create Email Rule From b631c2ee8089a63a4959297c29ec125b7dbe090c Mon Sep 17 00:00:00 2001 From: rockstardev <5191402+rockstardev@users.noreply.github.com> Date: Wed, 19 Mar 2025 21:31:46 -0500 Subject: [PATCH 14/15] Adding breadcrumbs per request --- .../Views/UIStores/StoreEmailRulesManage.cshtml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml index 4863fad01..563d2a749 100644 --- a/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml +++ b/BTCPayServer/Views/UIStores/StoreEmailRulesManage.cshtml @@ -15,7 +15,18 @@