From c11336c534504d09f251792be36efd3a860aef7f Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Tue, 30 Jul 2024 08:17:46 -0700 Subject: [PATCH 01/11] Email stubs --- Hippo.Core/Services/NotificationService.cs | 20 ++++++++++++++++++ Hippo.Core/Services/PaymentsService.cs | 24 ++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Hippo.Core/Services/NotificationService.cs b/Hippo.Core/Services/NotificationService.cs index b927f920..7f70886b 100644 --- a/Hippo.Core/Services/NotificationService.cs +++ b/Hippo.Core/Services/NotificationService.cs @@ -23,6 +23,10 @@ public interface INotificationService Task AccountDecision(Request request, bool isApproved, string overrideSponsor = null, string reason = null); Task AdminOverrideDecision(Request request, bool isApproved, User adminUser, string reason = null); Task SimpleNotification(SimpleNotificationModel simpleNotificationModel, string[] emails, string[] ccEmails = null); + + Task AdminPaymentFailureNotification(string[] emails, int[] orderIds); + Task SponsorPaymentFailureNotification(string[] emails, Order order); //Could possibly just pass the order Id, but there might be more order info we want to include + Task OrderNotification(SimpleNotificationModel simpleNotificationModel, Order order, string[] emails, string[] ccEmails = null); } public class NotificationService : INotificationService @@ -237,5 +241,21 @@ private async Task GetGroupAdminEmails(Group group) .ToArrayAsync(); return groupAdminEmails; } + + public Task AdminPaymentFailureNotification(string[] emails, int[] orderIds) + { + throw new NotImplementedException(); + } + + public Task SponsorPaymentFailureNotification(string[] emails, Order order) + { + throw new NotImplementedException(); + } + + public Task OrderNotification(SimpleNotificationModel simpleNotificationModel, Order order, string[] emails, string[] ccEmails = null) + { + //Can pass the simple part for the general text, and the order for the specific details + throw new NotImplementedException(); + } } } diff --git a/Hippo.Core/Services/PaymentsService.cs b/Hippo.Core/Services/PaymentsService.cs index 0ae22a63..90d3e68f 100644 --- a/Hippo.Core/Services/PaymentsService.cs +++ b/Hippo.Core/Services/PaymentsService.cs @@ -24,12 +24,14 @@ public class PaymentsService : IPaymentsService private readonly AppDbContext _dbContext; private readonly IHistoryService _historyService; private readonly IAggieEnterpriseService _aggieEnterpriseService; + private readonly INotificationService _notificationService; - public PaymentsService(AppDbContext dbContext, IHistoryService historyService, IAggieEnterpriseService aggieEnterpriseService) + public PaymentsService(AppDbContext dbContext, IHistoryService historyService, IAggieEnterpriseService aggieEnterpriseService, INotificationService notificationService) { _dbContext = dbContext; _historyService = historyService; _aggieEnterpriseService = aggieEnterpriseService; + _notificationService = notificationService; } public async Task CreatePayments() { @@ -164,8 +166,17 @@ public async Task NotifyAboutFailedPayments() } if (invalidChartStrings) { + Log.Error("Order {0} has an invalid chart string", order.Id); //TODO: Notify the sponsor //Remember to add notification/email service to job + try + { + await _notificationService.SponsorPaymentFailureNotification(new string[] { order.PrincipalInvestigator.Email }, order); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to notify sponsor for order {0}", order.Id); + } invalidOrderIdsInCluster.Add(order.Id); @@ -177,9 +188,18 @@ public async Task NotifyAboutFailedPayments() //Send email to cluster admins with list of orders that have failed payments //https://localhost:44371/caesfarm/order/details/46 - var clusterAdmins = await _dbContext.Users.AsNoTracking().Where(u => u.Permissions.Any(p => p.Cluster.Id == orderGroup.Key && p.Role.Name == Role.Codes.ClusterAdmin)).OrderBy(u => u.LastName).ThenBy(u => u.FirstName).ToArrayAsync(); + var clusterAdmins = await _dbContext.Users.AsNoTracking().Where(u => u.Permissions.Any(p => p.Cluster.Id == orderGroup.Key && p.Role.Name == Role.Codes.ClusterAdmin)).Select(a => a.Email).ToArrayAsync(); //TODO: Notify the cluster admins with a single email + try + { + await _notificationService.AdminPaymentFailureNotification(clusterAdmins, invalidOrderIdsInCluster.ToArray()); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to notify cluster admins for cluster {0}", cluster.Id); + } + } } From e70dd84dba3172d14aa001f7fd43ffb734a4b26b Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Tue, 30 Jul 2024 09:49:34 -0700 Subject: [PATCH 02/11] Sponsor/Admin order email notification --- Hippo.Web/Controllers/OrderController.cs | 77 +++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/Hippo.Web/Controllers/OrderController.cs b/Hippo.Web/Controllers/OrderController.cs index 723065b1..9c63aa20 100644 --- a/Hippo.Web/Controllers/OrderController.cs +++ b/Hippo.Web/Controllers/OrderController.cs @@ -10,6 +10,8 @@ using Microsoft.EntityFrameworkCore; using static Hippo.Core.Domain.Product; using static Hippo.Core.Models.SlothModels.TransferViewModel; +using Serilog; +using Hippo.Email.Models; namespace Hippo.Web.Controllers { @@ -21,14 +23,15 @@ public class OrderController : SuperController private readonly IAggieEnterpriseService _aggieEnterpriseService; private readonly IUserService _userService; private readonly IHistoryService _historyService; + private readonly INotificationService _notificationService; - - public OrderController(AppDbContext dbContext, IAggieEnterpriseService aggieEnterpriseService, IUserService userService, IHistoryService historyService) + public OrderController(AppDbContext dbContext, IAggieEnterpriseService aggieEnterpriseService, IUserService userService, IHistoryService historyService, INotificationService notificationService) { _dbContext = dbContext; _aggieEnterpriseService = aggieEnterpriseService; _userService = userService; _historyService = historyService; + _notificationService = notificationService; } @@ -334,6 +337,7 @@ public async Task ChangeStatus(int id, string expectedStatus) existingOrder.Status = Order.Statuses.Submitted; await _historyService.OrderSnapshot(existingOrder, currentUser, History.OrderActions.Updated); await _historyService.OrderUpdated(existingOrder, currentUser, "Order Submitted."); + await NotifyAdminOrderSubmitted(existingOrder); break; case Order.Statuses.Submitted: @@ -348,6 +352,7 @@ public async Task ChangeStatus(int id, string expectedStatus) existingOrder.Status = Order.Statuses.Processing; await _historyService.OrderSnapshot(existingOrder, currentUser, History.OrderActions.Updated); await _historyService.OrderUpdated(existingOrder, currentUser, "Order Processing."); + await NotifySponsorOrderStatusChange(existingOrder); break; case Order.Statuses.Processing: @@ -389,6 +394,8 @@ public async Task ChangeStatus(int id, string expectedStatus) await _historyService.OrderSnapshot(existingOrder, currentUser, History.OrderActions.Updated); await _historyService.OrderUpdated(existingOrder, currentUser, "Order Activated."); + await NotifySponsorOrderStatusChange(existingOrder); + break; default: return BadRequest("You cannot change the status of an order in the current status."); @@ -633,10 +640,76 @@ private async Task SaveNewOrder(OrderPostModel model, Account rtValue.Success = true; rtValue.Order = order; + + if(order.Status == Order.Statuses.Created) + { + //Notify the PI that they need to enter billing info and submit the order. + } + if(order.Status == Order.Statuses.Submitted) + { + //Notify the admins that a new order has been submitted. + //This can be done in a couple places, so private method + await NotifyAdminOrderSubmitted(order); + } return rtValue; } + private async Task NotifyAdminOrderSubmitted(Order order) + { + try + { + var clusterAdmins = await _dbContext.Users.AsNoTracking().Where(u => u.Permissions.Any(p => p.Cluster.Id == order.ClusterId && p.Role.Name == Role.Codes.ClusterAdmin)).Select(a => a.Email).ToArrayAsync(); + var emailModel = new SimpleNotificationModel + { + Subject = "New Order Submitted", + Header = "A new order has been submitted.", + Paragraphs = new List + { + $"A new order has been submitted by {order.PrincipalInvestigator.Owner.FirstName} {order.PrincipalInvestigator.Owner.LastName}.", + } + }; + + await _notificationService.OrderNotification(emailModel, order, clusterAdmins); + } + catch (Exception ex) + { + Log.Error(ex, "Error sending email to admins for new order submission."); + } + } + + private async Task NotifySponsorOrderStatusChange(Order order) + { + try + { + var emailModel = new SimpleNotificationModel + { + Subject = "Order Updated", + Header = "Order Status has changed", + Paragraphs = new List(), + }; + + if(order.Status == Order.Statuses.Processing) + { + emailModel.Paragraphs.Add("We have begun processing your order."); + } + if(order.Status == Order.Statuses.Active) + { + emailModel.Paragraphs.Add("Your order has been activated. Automatic billing will commence. You may also make out of cycle payments on your order."); + } + if(order.Status == Order.Statuses.Rejected) + { + emailModel.Paragraphs.Add("Your order has been rejected."); + } + + await _notificationService.OrderNotification(emailModel, order, new string[] {order.PrincipalInvestigator.Email}); + } + catch (Exception ex) + { + Log.Error(ex, "Error sending email to admins for new order submission."); + } + } + private async Task UpdateExistingOrder(OrderPostModel model, bool isClusterOrSystemAdmin, User currentUser) { var rtValue = new ProcessingResult(); From a8840c61f0e8e9caebed5d701d40b12e1ba722f5 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Tue, 30 Jul 2024 10:21:42 -0700 Subject: [PATCH 03/11] notifications --- Hippo.Web/Controllers/OrderController.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Hippo.Web/Controllers/OrderController.cs b/Hippo.Web/Controllers/OrderController.cs index 9c63aa20..1108a140 100644 --- a/Hippo.Web/Controllers/OrderController.cs +++ b/Hippo.Web/Controllers/OrderController.cs @@ -644,6 +644,7 @@ private async Task SaveNewOrder(OrderPostModel model, Account if(order.Status == Order.Statuses.Created) { //Notify the PI that they need to enter billing info and submit the order. + await NotifySponsorOrderCreatedByAdmin(order); } if(order.Status == Order.Statuses.Submitted) { @@ -678,6 +679,29 @@ private async Task NotifyAdminOrderSubmitted(Order order) } } + private async Task NotifySponsorOrderCreatedByAdmin(Order order) + { + try + { + var emailModel = new SimpleNotificationModel + { + Subject = "New Order Created", + Header = "A new order has been created for you.", + Paragraphs = new List + { + "A new order has been created for you. Please enter the billing information and approve it for processing.", + "If you believe this was done in error, please contact the cluster admins before canceleing it." + } + }; + + await _notificationService.OrderNotification(emailModel, order, new string[] {order.PrincipalInvestigator.Email}); + } + catch (Exception ex) + { + Log.Error(ex, "Error sending email to admins for new order submission."); + } + } + private async Task NotifySponsorOrderStatusChange(Order order) { try From 39b207fbbd55fc22f9fe53158e011c0e34aaf3c9 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Tue, 30 Jul 2024 12:27:16 -0700 Subject: [PATCH 04/11] WIP - Order Emails --- Hippo.Core/Services/EmailService.cs | 8 +++-- Hippo.Core/Services/NotificationService.cs | 36 +++++++++++++++++-- Hippo.Email/Models/OrderNotificationModel.cs | 22 ++++++++++++ .../Emails/OrderNotification_mjml.cshtml | 27 ++++++++++++++ Hippo.Web/Controllers/OrderController.cs | 28 +++++++++++---- 5 files changed, 109 insertions(+), 12 deletions(-) create mode 100644 Hippo.Email/Models/OrderNotificationModel.cs create mode 100644 Hippo.Email/Views/Emails/OrderNotification_mjml.cshtml diff --git a/Hippo.Core/Services/EmailService.cs b/Hippo.Core/Services/EmailService.cs index 933d7ce6..27c7fe02 100644 --- a/Hippo.Core/Services/EmailService.cs +++ b/Hippo.Core/Services/EmailService.cs @@ -52,10 +52,12 @@ public async Task SendEmail(EmailModel emailModel) { message.To.Add(new MailAddress(email, email)); } - - foreach (var ccEmail in emailModel.CcEmails) + if (emailModel.CcEmails != null) { - message.CC.Add(new MailAddress(ccEmail)); + foreach (var ccEmail in emailModel.CcEmails) + { + message.CC.Add(new MailAddress(ccEmail)); + } } if (!string.IsNullOrWhiteSpace(_emailSettings.BccEmail)) diff --git a/Hippo.Core/Services/NotificationService.cs b/Hippo.Core/Services/NotificationService.cs index 7f70886b..39880ed7 100644 --- a/Hippo.Core/Services/NotificationService.cs +++ b/Hippo.Core/Services/NotificationService.cs @@ -252,10 +252,40 @@ public Task SponsorPaymentFailureNotification(string[] emails, Order order throw new NotImplementedException(); } - public Task OrderNotification(SimpleNotificationModel simpleNotificationModel, Order order, string[] emails, string[] ccEmails = null) + public async Task OrderNotification(SimpleNotificationModel simpleNotificationModel, Order order, string[] emails, string[] ccEmails = null) { - //Can pass the simple part for the general text, and the order for the specific details - throw new NotImplementedException(); + try + { + var message = simpleNotificationModel.Paragraphs.FirstOrDefault(); + + var model = new OrderNotificationModel() + { + UcdLogoUrl = $"{_emailSettings.BaseUrl}/media/caes-logo-gray.png", + ButtonUrl = $"{_emailSettings.BaseUrl}/{order.Cluster.Name}/order/details/{order.Id}", + Subject = simpleNotificationModel.Subject, + Header = simpleNotificationModel.Header, + Paragraphs = simpleNotificationModel.Paragraphs, + }; + + + var htmlBody = await _mjmlRenderer.RenderView("/Views/Emails/OrderNotification_mjml.cshtml", model); + + await _emailService.SendEmail(new EmailModel + { + Emails = emails, + CcEmails = ccEmails, + HtmlBody = htmlBody, + TextBody = message, + Subject = simpleNotificationModel.Subject, + }); + + return true; + } + catch (Exception ex) + { + Log.Error("Error emailing Order Notification", ex); + return false; + } } } } diff --git a/Hippo.Email/Models/OrderNotificationModel.cs b/Hippo.Email/Models/OrderNotificationModel.cs new file mode 100644 index 00000000..0f11c645 --- /dev/null +++ b/Hippo.Email/Models/OrderNotificationModel.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Hippo.Email.Models +{ + public class OrderNotificationModel + { + + public string UcdLogoUrl { get; set; } = String.Empty; + + public string ButtonText { get; set; } = "View Order"; + public string ButtonUrl { get; set; } = ""; + + public string Subject { get; set; } = ""; + public string Header { get; set; } = ""; + public List Paragraphs { get; set; } = new(); + + } +} diff --git a/Hippo.Email/Views/Emails/OrderNotification_mjml.cshtml b/Hippo.Email/Views/Emails/OrderNotification_mjml.cshtml new file mode 100644 index 00000000..eee0c0fd --- /dev/null +++ b/Hippo.Email/Views/Emails/OrderNotification_mjml.cshtml @@ -0,0 +1,27 @@ +@using Hippo.Email.Models +@model Hippo.Email.Models.OrderNotificationModel + +@{ + ViewData["EmailTitle"] = "Order Notification"; + Layout = "_EmailLayout_mjml"; +} + + + + + +

@Model?.Header

+
+ + @foreach (var paragraph in @Model.Paragraphs) + { +

@paragraph

+ } +
+
+
+ +@await Html.PartialAsync("_EmailButton_mjml", new EmailButtonModel(@Model!.ButtonText, @Model!.ButtonUrl)) + + diff --git a/Hippo.Web/Controllers/OrderController.cs b/Hippo.Web/Controllers/OrderController.cs index 1108a140..1203c47a 100644 --- a/Hippo.Web/Controllers/OrderController.cs +++ b/Hippo.Web/Controllers/OrderController.cs @@ -12,6 +12,7 @@ using static Hippo.Core.Models.SlothModels.TransferViewModel; using Serilog; using Hippo.Email.Models; +using System.Runtime.CompilerServices; namespace Hippo.Web.Controllers { @@ -222,6 +223,7 @@ public async Task Save([FromBody] OrderPostModel model) { return BadRequest("Invalid Installment Type Detected."); } + var result = new ProcessingResult(); if (model.Id == 0) { @@ -231,6 +233,7 @@ public async Task Save([FromBody] OrderPostModel model) return BadRequest(processingResult.Message); } orderToReturn = processingResult.Order; + result = processingResult; } else { @@ -241,13 +244,26 @@ public async Task Save([FromBody] OrderPostModel model) } orderToReturn = processingResult.Order; - + result = processingResult; } await _dbContext.SaveChangesAsync(); + if(result.NotificationMethod != null && orderToReturn != null) + { + switch (result.NotificationMethod) + { + case "NotifyAdminOrderSubmitted": + await NotifyAdminOrderSubmitted(orderToReturn); + break; + case "NotifySponsorOrderCreatedByAdmin": + await NotifySponsorOrderCreatedByAdmin(orderToReturn); + break; + } + } + return Ok(orderToReturn); } @@ -641,16 +657,14 @@ private async Task SaveNewOrder(OrderPostModel model, Account rtValue.Success = true; rtValue.Order = order; + if(order.Status == Order.Statuses.Created) { - //Notify the PI that they need to enter billing info and submit the order. - await NotifySponsorOrderCreatedByAdmin(order); + rtValue.NotificationMethod = "NotifySponsorOrderCreatedByAdmin"; } if(order.Status == Order.Statuses.Submitted) { - //Notify the admins that a new order has been submitted. - //This can be done in a couple places, so private method - await NotifyAdminOrderSubmitted(order); + rtValue.NotificationMethod = "NotifyAdminOrderSubmitted"; } return rtValue; @@ -1004,6 +1018,8 @@ private class ProcessingResult public string? Message { get; set; } public Order? Order { get; set; } + + public string? NotificationMethod { get; set; } } } } From 59b8ef9259f31a7b188cc12aac7e421dc05e70a4 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Tue, 30 Jul 2024 12:40:21 -0700 Subject: [PATCH 05/11] it isn't null --- Hippo.Email/Views/Emails/OrderNotification_mjml.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hippo.Email/Views/Emails/OrderNotification_mjml.cshtml b/Hippo.Email/Views/Emails/OrderNotification_mjml.cshtml index eee0c0fd..df7a67fc 100644 --- a/Hippo.Email/Views/Emails/OrderNotification_mjml.cshtml +++ b/Hippo.Email/Views/Emails/OrderNotification_mjml.cshtml @@ -11,7 +11,7 @@ background-color="#ffffff" padding-bottom="20px" padding-top="20px"> -

@Model?.Header

+

@Model.Header

@foreach (var paragraph in @Model.Paragraphs) From 8d980839b31f43a69561756991922597b78f167a Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Tue, 30 Jul 2024 14:17:04 -0700 Subject: [PATCH 06/11] wip --- Hippo.Core/Services/NotificationService.cs | 38 ++++++++++++++++++++-- Hippo.Jobs.OrderProcess/Program.cs | 7 +++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Hippo.Core/Services/NotificationService.cs b/Hippo.Core/Services/NotificationService.cs index 39880ed7..84a2a1ab 100644 --- a/Hippo.Core/Services/NotificationService.cs +++ b/Hippo.Core/Services/NotificationService.cs @@ -247,9 +247,43 @@ public Task AdminPaymentFailureNotification(string[] emails, int[] orderId throw new NotImplementedException(); } - public Task SponsorPaymentFailureNotification(string[] emails, Order order) + public async Task SponsorPaymentFailureNotification(string[] emails, Order order) { - throw new NotImplementedException(); + try + { + var message = "The payment for the following order has failed. Please update your billing information in Hippo."; + + var model = new OrderNotificationModel() + { + UcdLogoUrl = $"{_emailSettings.BaseUrl}/media/caes-logo-gray.png", + ButtonUrl = $"{_emailSettings.BaseUrl}/{order.Cluster.Name}/order/details/{order.Id}", + Subject = "Payment failed", + Header = "Order Payment Failed", + Paragraphs = new List(), + }; + model.Paragraphs.Add("The payment for this order has failed."); + model.Paragraphs.Add("This is most likely due to a Aggie Enterprise Chart String which is no longer valid."); + model.Paragraphs.Add("The order details will have the validation message from Aggie Enterprise."); + model.Paragraphs.Add("Please update your billing information in Hippo."); + + var htmlBody = await _mjmlRenderer.RenderView("/Views/Emails/OrderNotification_mjml.cshtml", model); + + await _emailService.SendEmail(new EmailModel + { + Emails = emails, + CcEmails = null, + HtmlBody = htmlBody, + TextBody = message, + Subject = model.Subject, + }); + + return true; + } + catch (Exception ex) + { + Log.Error("Error emailing Sponsor Payment Failure Notification", ex); + return false; + } } public async Task OrderNotification(SimpleNotificationModel simpleNotificationModel, Order order, string[] emails, string[] ccEmails = null) diff --git a/Hippo.Jobs.OrderProcess/Program.cs b/Hippo.Jobs.OrderProcess/Program.cs index 728b5e85..501ff619 100644 --- a/Hippo.Jobs.OrderProcess/Program.cs +++ b/Hippo.Jobs.OrderProcess/Program.cs @@ -9,6 +9,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Mjml.Net; using Serilog; @@ -120,6 +121,7 @@ private static ServiceProvider ConfigureServices() services.Configure(Configuration.GetSection("Azure")); services.Configure(Configuration.GetSection("Sloth")); services.Configure(Configuration.GetSection("AggieEnterprise")); + services.Configure(Configuration.GetSection("Email")); services.AddScoped(); services.AddTransient(); @@ -127,7 +129,10 @@ private static ServiceProvider ConfigureServices() services.AddHttpClient(); services.AddSingleton(); services.AddScoped(); - //TODO: This will probably need the notification service as well. + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); return services.BuildServiceProvider(); From 2f4ee1854ea37549fcfcf546504dc239f3a3f64f Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Tue, 30 Jul 2024 14:47:11 -0700 Subject: [PATCH 07/11] Email from job --- Hippo.Jobs.OrderProcess/Program.cs | 2 +- Hippo.Jobs.OrderProcess/appsettings.json | 10 ++++++++++ Hippo.Web/appsettings.json | 1 - 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Hippo.Jobs.OrderProcess/Program.cs b/Hippo.Jobs.OrderProcess/Program.cs index 501ff619..125ca558 100644 --- a/Hippo.Jobs.OrderProcess/Program.cs +++ b/Hippo.Jobs.OrderProcess/Program.cs @@ -132,7 +132,7 @@ private static ServiceProvider ConfigureServices() services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + return services.BuildServiceProvider(); diff --git a/Hippo.Jobs.OrderProcess/appsettings.json b/Hippo.Jobs.OrderProcess/appsettings.json index a859e825..d797ab13 100644 --- a/Hippo.Jobs.OrderProcess/appsettings.json +++ b/Hippo.Jobs.OrderProcess/appsettings.json @@ -30,6 +30,16 @@ "ApiUrl": "https://sloth-api-test.azurewebsites.net/v2/", "HippoBaseUrl": "https://hippo-test.azurewebsites.net/" }, + "Email": { + "ApiKey": "[External]", + "UserName": "[External]", + "Password": "[External]", + "Host": "[External]", + "Port": 1, + "FromEmail": "hippo@notify.ucdavis.edu", + "FromName": "Hippo Notification", + "BaseUrl": "https://localhost:44371" + }, "ConnectionStrings": { "DefaultConnection": "[External]" } diff --git a/Hippo.Web/appsettings.json b/Hippo.Web/appsettings.json index 073924f2..e88ce515 100644 --- a/Hippo.Web/appsettings.json +++ b/Hippo.Web/appsettings.json @@ -20,7 +20,6 @@ }, "Email": { "ApiKey": "[External]", - "DisableSend": "Yes", "UserName": "[External]", "Password": "[External]", "Host": "[External]", From a809f8a351d0e1968b6fa809aafbccb2de8ff843 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Tue, 30 Jul 2024 14:47:54 -0700 Subject: [PATCH 08/11] Remove UserService from notifications --- Hippo.Core/Services/NotificationService.cs | 17 +++-------------- Hippo.Web/Controllers/RequestController.cs | 6 +++--- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/Hippo.Core/Services/NotificationService.cs b/Hippo.Core/Services/NotificationService.cs index 84a2a1ab..d3feb024 100644 --- a/Hippo.Core/Services/NotificationService.cs +++ b/Hippo.Core/Services/NotificationService.cs @@ -20,7 +20,7 @@ namespace Hippo.Core.Services public interface INotificationService { Task AccountRequest(Request request); - Task AccountDecision(Request request, bool isApproved, string overrideSponsor = null, string reason = null); + Task AccountDecision(Request request, bool isApproved, string decidedBy , string reason = null); Task AdminOverrideDecision(Request request, bool isApproved, User adminUser, string reason = null); Task SimpleNotification(SimpleNotificationModel simpleNotificationModel, string[] emails, string[] ccEmails = null); @@ -34,33 +34,22 @@ public class NotificationService : INotificationService private readonly AppDbContext _dbContext; private readonly IEmailService _emailService; private readonly EmailSettings _emailSettings; - private readonly IUserService _userService; private readonly IMjmlRenderer _mjmlRenderer; public NotificationService(AppDbContext dbContext, IEmailService emailService, - IOptions emailSettings, IUserService userService, IMjmlRenderer mjmlRenderer) + IOptions emailSettings, IMjmlRenderer mjmlRenderer) { _dbContext = dbContext; _emailService = emailService; _emailSettings = emailSettings.Value; - _userService = userService; _mjmlRenderer = mjmlRenderer; } - public async Task AccountDecision(Request request, bool isApproved, string overrideDecidedBy = null, string details = "") + public async Task AccountDecision(Request request, bool isApproved, string decidedBy, string details = "") { try { - var decidedBy = String.Empty; - if (!string.IsNullOrWhiteSpace(overrideDecidedBy)) - { - decidedBy = overrideDecidedBy; - } - else - { - decidedBy = (await _userService.GetCurrentUser()).Name; - } var requestUrl = $"{_emailSettings.BaseUrl}/{request.Cluster.Name}"; //TODO: Only have button if approved? var emailTo = request.Requester.Email; diff --git a/Hippo.Web/Controllers/RequestController.cs b/Hippo.Web/Controllers/RequestController.cs index 46c0d6eb..00774ba6 100644 --- a/Hippo.Web/Controllers/RequestController.cs +++ b/Hippo.Web/Controllers/RequestController.cs @@ -128,7 +128,7 @@ private async Task ApproveCreateAccount(AccountRequest request) request.Status = AccountRequest.Statuses.Processing; request.UpdatedOn = DateTime.UtcNow; - var success = await _notificationService.AccountDecision(request, true, + var success = await _notificationService.AccountDecision(request, true, decidedBy: currentUser.Name, reason: "Your account request has been approved. You will receive another email with more details once your " + $"account is created on {Cluster}. You can check the \"My Account\" tab of Hippo to see what " + "resources you have access to."); @@ -183,7 +183,7 @@ private async Task ApproveAddAccountToGroup(AccountRequest request request.Status = AccountRequest.Statuses.Processing; request.UpdatedOn = DateTime.UtcNow; - var success = await _notificationService.AccountDecision(request, true); + var success = await _notificationService.AccountDecision(request, true, currentUser.Name); if (!success) { Log.Error("Error creating Account Decision email"); @@ -240,7 +240,7 @@ public async Task Reject(int id, [FromBody] RequestRejectionModel request.Status = AccountRequest.Statuses.Rejected; request.UpdatedOn = DateTime.UtcNow; - var success = await _notificationService.AccountDecision(request, false, reason: model.Reason); + var success = await _notificationService.AccountDecision(request, false, decidedBy: currentUser.Name, reason: model.Reason); if (!success) { Log.Error("Error creating Account Decision email"); From b519dc1a212540f8bd23fbe9350ee22fd2605f20 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Tue, 30 Jul 2024 15:44:32 -0700 Subject: [PATCH 09/11] admin payment fail notification --- Hippo.Core/Services/NotificationService.cs | 40 +++++++++++++++-- Hippo.Core/Services/PaymentsService.cs | 2 +- Hippo.Email/Hippo.Email.csproj | 8 +++- .../Emails/OrderAdminPaymentFail_mjml.cshtml | 43 +++++++++++++++++++ 4 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 Hippo.Email/Views/Emails/OrderAdminPaymentFail_mjml.cshtml diff --git a/Hippo.Core/Services/NotificationService.cs b/Hippo.Core/Services/NotificationService.cs index d3feb024..5b3cf4e9 100644 --- a/Hippo.Core/Services/NotificationService.cs +++ b/Hippo.Core/Services/NotificationService.cs @@ -24,7 +24,7 @@ public interface INotificationService Task AdminOverrideDecision(Request request, bool isApproved, User adminUser, string reason = null); Task SimpleNotification(SimpleNotificationModel simpleNotificationModel, string[] emails, string[] ccEmails = null); - Task AdminPaymentFailureNotification(string[] emails, int[] orderIds); + Task AdminPaymentFailureNotification(string[] emails, string clusterName, int[] orderIds); Task SponsorPaymentFailureNotification(string[] emails, Order order); //Could possibly just pass the order Id, but there might be more order info we want to include Task OrderNotification(SimpleNotificationModel simpleNotificationModel, Order order, string[] emails, string[] ccEmails = null); } @@ -231,9 +231,43 @@ private async Task GetGroupAdminEmails(Group group) return groupAdminEmails; } - public Task AdminPaymentFailureNotification(string[] emails, int[] orderIds) + public async Task AdminPaymentFailureNotification(string[] emails, string clusterName, int[] orderIds) { - throw new NotImplementedException(); + try + { + var message = "The payment for one or more orders in hippo have failed."; + + var model = new OrderNotificationModel() + { + UcdLogoUrl = $"{_emailSettings.BaseUrl}/media/caes-logo-gray.png", + Subject = "Payment failed", + Header = "Order Payment Failed", + Paragraphs = new List(), + }; + foreach (var orderId in orderIds) + { + model.Paragraphs.Add($"{_emailSettings.BaseUrl}/{clusterName}/order/details/{orderId}"); + + } + + var htmlBody = await _mjmlRenderer.RenderView("/Views/Emails/OrderAdminPaymentFail_mjml.cshtml", model); + + await _emailService.SendEmail(new EmailModel + { + Emails = emails, + CcEmails = null, + HtmlBody = htmlBody, + TextBody = message, + Subject = model.Subject, + }); + + return true; + } + catch (Exception ex) + { + Log.Error("Error emailing Sponsor Payment Failure Notification", ex); + return false; + } } public async Task SponsorPaymentFailureNotification(string[] emails, Order order) diff --git a/Hippo.Core/Services/PaymentsService.cs b/Hippo.Core/Services/PaymentsService.cs index 90d3e68f..7a871871 100644 --- a/Hippo.Core/Services/PaymentsService.cs +++ b/Hippo.Core/Services/PaymentsService.cs @@ -193,7 +193,7 @@ public async Task NotifyAboutFailedPayments() try { - await _notificationService.AdminPaymentFailureNotification(clusterAdmins, invalidOrderIdsInCluster.ToArray()); + await _notificationService.AdminPaymentFailureNotification(clusterAdmins, cluster.Name, invalidOrderIdsInCluster.ToArray()); } catch (Exception ex) { diff --git a/Hippo.Email/Hippo.Email.csproj b/Hippo.Email/Hippo.Email.csproj index 515f8339..16e7767d 100644 --- a/Hippo.Email/Hippo.Email.csproj +++ b/Hippo.Email/Hippo.Email.csproj @@ -7,10 +7,16 @@ enable - + + + + true + PreserveNewest + + diff --git a/Hippo.Email/Views/Emails/OrderAdminPaymentFail_mjml.cshtml b/Hippo.Email/Views/Emails/OrderAdminPaymentFail_mjml.cshtml new file mode 100644 index 00000000..050ad5e3 --- /dev/null +++ b/Hippo.Email/Views/Emails/OrderAdminPaymentFail_mjml.cshtml @@ -0,0 +1,43 @@ +@using Hippo.Email.Models +@model Hippo.Email.Models.OrderNotificationModel + +@{ + ViewData["EmailTitle"] = "Order Notification"; + Layout = "_EmailLayout_mjml"; +} + + + + + +

@Model.Header

+
+ +

There are one or more orders that have failing payments.

+

The Sponsor has been notified, and until the billing is corrected the payment can't go through

+
+
+
+ + + + + +

Orders With Failing Payments

+
+
+
+ + + + @foreach (var paragraph in @Model.Paragraphs) + { +

@paragraph

+ } +
+
+
+ From 9aae9619d4d71f7d458f597409e2e34e84055af0 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Wed, 31 Jul 2024 07:36:31 -0700 Subject: [PATCH 10/11] remove proj change --- Hippo.Email/Hippo.Email.csproj | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Hippo.Email/Hippo.Email.csproj b/Hippo.Email/Hippo.Email.csproj index 16e7767d..31be7eac 100644 --- a/Hippo.Email/Hippo.Email.csproj +++ b/Hippo.Email/Hippo.Email.csproj @@ -12,11 +12,5 @@ - - - true - PreserveNewest - - From 1ef61d51ec7e61a5f4ae29d637e6d94aae669310 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Wed, 31 Jul 2024 07:44:46 -0700 Subject: [PATCH 11/11] cleanup --- Hippo.Core/Services/NotificationService.cs | 2 ++ Hippo.Email/Views/Emails/OrderAdminPaymentFail_mjml.cshtml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Hippo.Core/Services/NotificationService.cs b/Hippo.Core/Services/NotificationService.cs index 5b3cf4e9..d4a77e9f 100644 --- a/Hippo.Core/Services/NotificationService.cs +++ b/Hippo.Core/Services/NotificationService.cs @@ -284,6 +284,8 @@ public async Task SponsorPaymentFailureNotification(string[] emails, Order Header = "Order Payment Failed", Paragraphs = new List(), }; + model.Paragraphs.Add($"Order: {order.Name}"); + model.Paragraphs.Add($"Order Id: {order.Id}"); model.Paragraphs.Add("The payment for this order has failed."); model.Paragraphs.Add("This is most likely due to a Aggie Enterprise Chart String which is no longer valid."); model.Paragraphs.Add("The order details will have the validation message from Aggie Enterprise."); diff --git a/Hippo.Email/Views/Emails/OrderAdminPaymentFail_mjml.cshtml b/Hippo.Email/Views/Emails/OrderAdminPaymentFail_mjml.cshtml index 050ad5e3..c918288a 100644 --- a/Hippo.Email/Views/Emails/OrderAdminPaymentFail_mjml.cshtml +++ b/Hippo.Email/Views/Emails/OrderAdminPaymentFail_mjml.cshtml @@ -15,7 +15,7 @@

There are one or more orders that have failing payments.

-

The Sponsor has been notified, and until the billing is corrected the payment can't go through

+

The Sponsor has been notified, and until the billing is corrected the payment can't go through.