diff --git a/.ai/agents/notification-specialist.md b/.ai/agents/notification-specialist.md index 6d3bb52d80..2928b2208f 100644 --- a/.ai/agents/notification-specialist.md +++ b/.ai/agents/notification-specialist.md @@ -1,6 +1,6 @@ --- name: Notification Specialist -description: Work with message templates, DotLiquid tokens and drops, IMessageProviderService, queued email lifecycle, LiquidObjectBuilder, and domain event notification handlers. +description: Work with message templates, DotLiquid tokens and drops, the per-area message provider services (IMessageProviderService) and IMessageProviderService, queued email lifecycle, LiquidObjectBuilder, and domain event notification handlers. tools: ["codebase", "search", "edit", "terminal"] --- @@ -10,7 +10,7 @@ which remain the single source of truth. Do not duplicate rules here. --> Read `AGENTS.md` first. Then read `.ai/skills/message-notification.md` for message templates, -DotLiquid tokens and drops, `IMessageProviderService`, queued email lifecycle, +DotLiquid tokens and drops, the per-area `IMessageProviderService` sends and `IMessageProviderService.SendNotification`, queued email lifecycle, `LiquidObjectBuilder`, the `MessageTokensAddedEvent` plugin extension point, and domain event notification handler rules. Run `.ai/checklists/definition-of-done.md` before reporting the change complete. diff --git a/.ai/skills/message-notification.md b/.ai/skills/message-notification.md index c5ed6194c5..015d0a7530 100644 --- a/.ai/skills/message-notification.md +++ b/.ai/skills/message-notification.md @@ -32,18 +32,18 @@ Do not use this skill as the primary review for MongoDB queries or security; com 6. Use DotLiquid syntax: `{{Drop.Property}}` for values, `{% if condition %}...{% endif %}` for conditions, `{% for item in Collection %}...{% endfor %}` for loops. #### Sending Messages -7. Add a typed send method to `IMessageProviderService` in `src/Business/Grand.Business.Core/Interfaces/Messages/IMessageProviderService.cs` when the trigger is a recurring, well-defined business event. Follow the signature pattern: +7. Add a typed send method to the area interface that owns the recipient's flow, `IMessageProviderService` in `src/Business/Grand.Business.Core/Interfaces/Messages/` (Customer, Order, Newsletter, Catalog, MerchandiseReturn, Vendor, Cms), when the trigger is a recurring, well-defined business event. Follow the signature pattern: ```csharp Task SendXxxMessage(TEntity entity, Store store, string languageId); ``` -8. Implement the method in `MessageProviderService` at `src/Business/Grand.Business.Messages/Services/MessageProviderService.cs`. The standard implementation pattern is: - 1. Get `MessageTemplate` by name using `IMessageTemplateService.GetMessageTemplateByName(name, store.Id)`. - 2. Return 0 if the template is not active. - 3. Resolve `EmailAccount` using the template's `EmailAccountId` and `IEmailAccountService`. +8. Implement the method in the matching `MessageProviderService` in `src/Business/Grand.Business.Messages/Services/`. The standard implementation pattern is: + 1. Get the active `MessageTemplate` with `IMessageLookupService.GetMessageTemplate(name, store.Id)`. + 2. Return 0 when it returns null (missing or inactive template). + 3. Resolve `EmailAccount` with `IMessageLookupService.GetEmailAccountOfMessageTemplate`, and the language with `EnsureLanguageIsActive`. 4. Build tokens with `LiquidObjectBuilder` using the appropriate `AddXxxTokens()` methods. - 5. Call `await builder.Build()` to get the populated `LiquidObject`. - 6. Call `await SendNotification(messageTemplate, emailAccount, languageId, liquidObject, toEmail, toName)`. -9. Use the generic `SendNotification` overload directly when a one-off or plugin-triggered send does not justify a dedicated typed method. + 5. Call `await builder.BuildAsync()` to get the populated `LiquidObject`. + 6. Call `await _messageProviderService.SendNotification(messageTemplate, emailAccount, languageId, liquidObject, toEmail, toName)`. +9. Use the generic `IMessageProviderService.SendNotification` directly when a one-off or plugin-triggered send does not justify a dedicated typed method. #### DotLiquid Drops and Tokens 10. Add new token properties to an existing `Liquid*` drop class in `src/Business/Grand.Business.Messages/DotLiquidDrops/` when the data already belongs to that drop's entity. Do not add properties that require loading additional entities unless those are already available in the drop constructor. @@ -66,7 +66,7 @@ Do not use this skill as the primary review for MongoDB queries or security; com 2. Prefer the `MessageTokensAddedEvent` handler for plugin-specific tokens rather than modifying core drop classes. 3. Prefer reusing `{{Store.Name}}`, `{{Store.URL}}`, and `{{Store.AdminEmail}}` tokens that are always available via `LiquidStore`. 4. Prefer the `Reference` enum field on `QueuedEmail` (e.g., `Reference.Order`, `Reference.Customer`) to link queued emails to the source entity for audit purposes. -5. Prefer testing `MessageProviderService` with a mock `IMessageTemplateService` that returns a pre-built `MessageTemplate` to verify the queuing call. +5. Prefer testing an `MessageProviderService` built on a real `MessageLookupService` and `MessageProviderService` with a mock `IMessageTemplateService` that returns a pre-built `MessageTemplate` to verify the queuing call. ## Constraints - Never hardcode email addresses in `SendXxx` methods; always resolve them from `EmailAccount` or from the entity being notified. @@ -74,19 +74,30 @@ Do not use this skill as the primary review for MongoDB queries or security; com - Never use tokens that are not backed by a drop property — they will render as empty strings. - Never add rendering logic (formatting, currency, date) to drop property getters unless the same pattern is already used in that drop. - Never store multiple language copies of a template as separate `MessageTemplate` documents; use the `Locales` collection. -- Never call `IMessageProviderService` from inside a Razor view or a controller action directly — trigger sends from handlers or services. +- Never call a message provider service from inside a Razor view or a controller action directly — trigger sends from handlers or services. ## Key Contracts -### IMessageProviderService (`src/Business/Grand.Business.Core/Interfaces/Messages/IMessageProviderService.cs`) -Typed send methods follow the pattern: +### Message provider services (`src/Business/Grand.Business.Core/Interfaces/Messages/`) +Typed send methods live in one interface per area, one method per template: ```csharp -Task SendCustomerRegisteredMessage(Customer customer, Store store, string languageId); -Task SendOrderPlacedCustomerMessage(Order order, Customer customer, Store store, string languageId); -// ... one method per template +// ICustomerMessageProviderService +Task SendCustomerRegisteredMessage(Customer customer, Store store, string languageId, DomainHost host = null); +// IOrderMessageProviderService +Task SendOrderPlacedCustomerMessage(Order order, Customer customer, string languageId, ...); ``` -Generic send: +| Interface | Covers | +|---|---| +| `ICustomerMessageProviderService` | registration, welcome, email validation, password recovery, customer notes, account deletion, contact us | +| `IOrderMessageProviderService` | order placed/paid/completed/cancelled/refunded for customer, store owner and vendor, shipments, order notes, gift vouchers | +| `INewsletterMessageProviderService` | newsletter subscription activation and deactivation | +| `ICatalogMessageProviderService` | email a friend, wishlist, product questions and reviews, low stock, back in stock, auctions and bids | +| `IMerchandiseReturnMessageProviderService` | merchandise return created, status changed, notes | +| `IVendorMessageProviderService` | vendor application, information change, vendor reviews, contact vendor | +| `ICmsMessageProviderService` | blog, knowledgebase article and news comments | + +They share `IMessageLookupService` (store, active template, email account, language) and queue through `IMessageProviderService`, which holds only the generic send: ```csharp Task SendNotification( MessageTemplate messageTemplate, EmailAccount emailAccount, @@ -105,7 +116,7 @@ var liquidObject = await new LiquidObjectBuilder(_mediator) .AddStoreTokens(store, language, emailAccount) .AddOrderTokens(order, customer, store, host) .AddCustomerTokens(customer, store, host, language) - .Build(); + .BuildAsync(); ``` The drops, the builder and the token commands belong to `Grand.Business.Messages`, not to `Grand.Business.Core`. Other Business projects and plugins compile against Core only, so they never use them directly: a campaign renders through `ICampaignMessageRenderer`, and a plugin adds tokens through `MessageTokensAddedEvent` / `EntityTokensAddedEvent` and `LiquidObject.AdditionalTokens`, which expose drops only as `DotLiquid.Drop`. @@ -126,8 +137,9 @@ public class MessageTokensAddedEvent : INotification { | QueuedEmail entity | `src/Core/Grand.Domain/Messages/QueuedEmail.cs` | | EmailAccount entity | `src/Core/Grand.Domain/Messages/EmailAccount.cs` | | MessageTemplateNames constants | `src/Business/Grand.Business.Messages/Services/MessageTemplateNames.cs` | -| IMessageProviderService interface | `src/Business/Grand.Business.Core/Interfaces/Messages/IMessageProviderService.cs` | -| MessageProviderService implementation | `src/Business/Grand.Business.Messages/Services/MessageProviderService.cs` | +| IMessageProviderService (generic `SendNotification`) | `src/Business/Grand.Business.Core/Interfaces/Messages/IMessageProviderService.cs` | +| I<Area>MessageProviderService interfaces, IMessageLookupService | `src/Business/Grand.Business.Core/Interfaces/Messages/` | +| Implementations | `src/Business/Grand.Business.Messages/Services/MessageProviderService.cs`, `MessageLookupService.cs`, `MessageProviderService.cs` | | IMessageTemplateService interface | `src/Business/Grand.Business.Core/Interfaces/Messages/IMessageTemplateService.cs` | | IQueuedEmailService interface | `src/Business/Grand.Business.Core/Interfaces/Messages/IQueuedEmailService.cs` | | IEmailAccountService interface | `src/Business/Grand.Business.Core/Interfaces/Messages/IEmailAccountService.cs` | @@ -191,9 +203,9 @@ Input: Send an email to the customer when an order reaches a custom "Ready for p Output: 1. Add `const string SendOrderReadyForPickupCustomerMessage = "OrderReadyForPickup.CustomerNotification"` to `MessageTemplateNames`. 2. Seed the template in `InstallDataMessageTemplates` with subject `"Your order {{Order.OrderNumber}} is ready"` and a body using `{{Order.*}}` and `{{Store.*}}` tokens. -3. Add `Task SendOrderReadyForPickupCustomerMessage(Order order, Customer customer, Store store, string languageId)` to `IMessageProviderService`. -4. Implement the method in `MessageProviderService`: load template by name, return 0 if inactive, build `LiquidObjectBuilder` with `AddStoreTokens` + `AddOrderTokens` + `AddCustomerTokens`, call `SendNotification` with the customer's email. -5. Publish `OrderReadyForPickupEvent` from the order status-change service and handle it in a `INotificationHandler` that calls `_messageProviderService.SendOrderReadyForPickupCustomerMessage(...)`. +3. Add `Task SendOrderReadyForPickupCustomerMessage(Order order, Customer customer, Store store, string languageId)` to `IOrderMessageProviderService`. +4. Implement the method in `OrderMessageProviderService`: load template by name, return 0 if inactive, build `LiquidObjectBuilder` with `AddStoreTokens` + `AddOrderTokens` + `AddCustomerTokens`, call `SendNotification` with the customer's email. +5. Publish `OrderReadyForPickupEvent` from the order status-change service and handle it in a `INotificationHandler` that calls `_orderMessageProviderService.SendOrderReadyForPickupCustomerMessage(...)` on the injected `IOrderMessageProviderService`. ### Example 2: Plugin Token Extension Input: A loyalty plugin needs to include a customer's loyalty points balance in existing order emails. diff --git a/.ai/skills/scheduled-task.md b/.ai/skills/scheduled-task.md index 84c5342412..f83f61905d 100644 --- a/.ai/skills/scheduled-task.md +++ b/.ai/skills/scheduled-task.md @@ -80,7 +80,7 @@ Do not use this skill as the primary review for MongoDB query safety or security - Never register a task with a DI key that differs from its `ScheduleTaskName` in the database. - Never add a seed entry without also adding a DI registration, and vice versa. - Never introduce locking, semaphores, or static shared state inside a task class. -- Never call `IEmailSender` directly from a task; use `IMessageProviderService` or queue via `IQueuedEmailService` following the message-notification pattern. +- Never call `IEmailSender` directly from a task; use a message provider service or queue via `IQueuedEmailService` following the message-notification pattern. ## Key Contracts diff --git a/AGENTS.md b/AGENTS.md index 8fa4393e86..4814053a9c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,7 +73,7 @@ A change that crosses domains loads every skill it touches. | `.ai/skills/admin-area-changes.md` | anything admin-facing that may reach Main Admin, Store Owner or Vendor: shared models, permissions, navigation, validation, scoped data | | `.ai/skills/admin-ui-component-layer.md` | building or converting an admin screen, tab or popup: ``, ``, ``, ``, ``, button hierarchy, Add new placement, known CSS traps | | `.ai/skills/settings-and-localization.md` | settings classes, store-scoped overrides, `ISettingService`, translation resources, `ITranslationService`, `IPluginTranslateResource`, translated entities and admin models | -| `.ai/skills/message-notification.md` | message templates, DotLiquid tokens and drops, `IMessageProviderService`, queued email lifecycle, `LiquidObjectBuilder`, `MessageTokensAddedEvent`, notification handlers | +| `.ai/skills/message-notification.md` | message templates, DotLiquid tokens and drops, message provider services (`IMessageProviderService`, `IMessageProviderService`), queued email lifecycle, `LiquidObjectBuilder`, `MessageTokensAddedEvent`, notification handlers | | `.ai/skills/scheduled-task.md` | `IScheduleTask`, `AddKeyedScoped` registration, `ScheduleTask` seed, distributed locking across instances, error handling, task migrations | | `.ai/skills/permission-navigation.md` | `PermissionSystemName`, `PermissionActionName`, `StandardPermission`, `PermissionProvider`, controller authorization attributes, `AdminSiteMap` entries and their migrations | diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ActivatedValueForPurchasedVouchersCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ActivatedValueForPurchasedVouchersCommandHandler.cs index a58a51574f..24d775bca3 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ActivatedValueForPurchasedVouchersCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ActivatedValueForPurchasedVouchersCommandHandler.cs @@ -13,16 +13,16 @@ public class { private readonly IGiftVoucherService _giftVoucherService; private readonly ILanguageService _languageService; - private readonly IMessageProviderService _messageProviderService; + private readonly IOrderMessageProviderService _orderMessageProviderService; public ActivatedValueForPurchasedGiftVouchersCommandHandler( IGiftVoucherService giftVoucherService, ILanguageService languageService, - IMessageProviderService messageProviderService) + IOrderMessageProviderService orderMessageProviderService) { _giftVoucherService = giftVoucherService; _languageService = languageService; - _messageProviderService = messageProviderService; + _orderMessageProviderService = orderMessageProviderService; } public async Task Handle(ActivatedValueForPurchasedGiftVouchersCommand request, @@ -48,7 +48,7 @@ await _languageService.GetLanguageById(request.Order.CustomerLanguageId) ?? (await _languageService.GetAllLanguages()).FirstOrDefault(); if (customerLang == null) throw new Exception("No languages could be loaded"); - _ = await _messageProviderService.SendGiftVoucherMessage(gc, request.Order, + _ = await _orderMessageProviderService.SendGiftVoucherMessage(gc, request.Order, customerLang.Id); gc.IsRecipientNotified = true; } diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/OrderNotificationCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/OrderNotificationCommandHandler.cs index dd5618da91..115ecd51e9 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/OrderNotificationCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/OrderNotificationCommandHandler.cs @@ -13,7 +13,7 @@ namespace Grand.Business.Checkout.Commands.Handlers.Orders; public class OrderNotificationCommandHandler : IRequestHandler { private readonly IOrderService _orderService; - private readonly IMessageProviderService _messageProviderService; + private readonly IOrderMessageProviderService _orderMessageProviderService; private readonly OrderSettings _orderSettings; private readonly LanguageSettings _languageSettings; private readonly IPdfService _pdfService; @@ -22,7 +22,7 @@ public class OrderNotificationCommandHandler : IRequestHandler(); - await _messageProviderService + await _orderMessageProviderService .SendOrderCompletedCustomerMessage(request.Order, customer, request.Order.CustomerLanguageId, orderCompletedAttachmentFilePath, orderCompletedAttachmentFileName, orderCompletedAttachments); @@ -90,7 +90,7 @@ await _messageProviderService && request.NotifyCustomer) { //notification customer - await _messageProviderService.SendOrderCancelledCustomerMessage(request.Order, customer, + await _orderMessageProviderService.SendOrderCancelledCustomerMessage(request.Order, customer, request.Order.CustomerLanguageId); //notification for vendor @@ -103,7 +103,7 @@ await _messageProviderService.SendOrderCancelledCustomerMessage(request.Order, c { //notification store owner var orderCancelledStoreOwnerNotificationQueuedEmailId = - await _messageProviderService.SendOrderCancelledStoreOwnerMessage(request.Order, customer, + await _orderMessageProviderService.SendOrderCancelledStoreOwnerMessage(request.Order, customer, request.Order.CustomerLanguageId); if (orderCancelledStoreOwnerNotificationQueuedEmailId > 0) await _orderService.InsertOrderNote(new OrderNote { @@ -147,7 +147,7 @@ private async Task VendorNotification(Order order) if (string.IsNullOrEmpty(orderItem.VendorId)) continue; var vendor = await _vendorService.GetVendorById(orderItem.VendorId); if (vendor is { Deleted: false, Active: true }) - await _messageProviderService.SendOrderCancelledVendorMessage(order, vendor, order.CustomerLanguageId); + await _orderMessageProviderService.SendOrderCancelledVendorMessage(order, vendor, order.CustomerLanguageId); } } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PartiallyRefundCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PartiallyRefundCommandHandler.cs index e70b434b07..600add5ee5 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PartiallyRefundCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PartiallyRefundCommandHandler.cs @@ -18,7 +18,7 @@ public class PartiallyRefundCommandHandler : IRequestHandler _logger; private readonly IMediator _mediator; - private readonly IMessageProviderService _messageProviderService; + private readonly IOrderMessageProviderService _orderMessageProviderService; private readonly IOrderService _orderService; private readonly IPaymentService _paymentService; private readonly IPaymentTransactionService _paymentTransactionService; @@ -27,7 +27,7 @@ public PartiallyRefundCommandHandler( IPaymentService paymentService, IPaymentTransactionService paymentTransactionService, IMediator mediator, - IMessageProviderService messageProviderService, + IOrderMessageProviderService orderMessageProviderService, ILogger logger, IOrderService orderService, LanguageSettings languageSettings) @@ -36,7 +36,7 @@ public PartiallyRefundCommandHandler( _paymentTransactionService = paymentTransactionService; _orderService = orderService; _mediator = mediator; - _messageProviderService = messageProviderService; + _orderMessageProviderService = orderMessageProviderService; _logger = logger; _languageSettings = languageSettings; } @@ -90,10 +90,10 @@ await _mediator.Send( await _mediator.Send(new CheckOrderStatusCommand { Order = order }, cancellationToken); //notifications - await _messageProviderService.SendOrderRefundedStoreOwnerMessage(order, amountToRefund, + await _orderMessageProviderService.SendOrderRefundedStoreOwnerMessage(order, amountToRefund, _languageSettings.DefaultAdminLanguageId); - await _messageProviderService.SendOrderRefundedCustomerMessage(order, amountToRefund, + await _orderMessageProviderService.SendOrderRefundedCustomerMessage(order, amountToRefund, order.CustomerLanguageId); } diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PartiallyRefundOfflineCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PartiallyRefundOfflineCommandHandler.cs index efc467951d..21641606ac 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PartiallyRefundOfflineCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PartiallyRefundOfflineCommandHandler.cs @@ -15,7 +15,7 @@ public class PartiallyRefundOfflineCommandHandler : IRequestHandler _logger; private readonly IMediator _mediator; - private readonly IMessageProviderService _messageProviderService; + private readonly ICatalogMessageProviderService _catalogMessageProviderService; private readonly IOrderService _orderService; private readonly OrderSettings _orderSettings; private readonly OidcLoginConfig _oidcLoginConfig; @@ -84,7 +84,7 @@ public PlaceOrderCommandHandler( ICustomerService customerService, IDiscountService discountService, IGroupService groupService, - IMessageProviderService messageProviderService, + ICatalogMessageProviderService catalogMessageProviderService, IVendorService vendorService, ISalesEmployeeService salesEmployeeService, ICurrencyService currencyService, @@ -117,7 +117,7 @@ public PlaceOrderCommandHandler( _customerService = customerService; _groupService = groupService; _discountService = discountService; - _messageProviderService = messageProviderService; + _catalogMessageProviderService = catalogMessageProviderService; _vendorService = vendorService; _salesEmployeeService = salesEmployeeService; _currencyService = currencyService; @@ -812,7 +812,7 @@ protected virtual async Task UpdateAuctionEnded(ShoppingCartItem sc, Product pro { await _auctionService.UpdateAuctionEnded(product, true, true); await _auctionService.UpdateHighestBid(product, product.Price, order.CustomerId); - await _messageProviderService.SendAuctionEndedBinCustomerMessage(product, order.CustomerId, + await _catalogMessageProviderService.SendAuctionEndedBinCustomerMessage(product, order.CustomerId, order.CustomerLanguageId, order.StoreId); await _auctionService.InsertBid(new Bid { CustomerId = order.CustomerId, diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ProcessOrderPaidCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ProcessOrderPaidCommandHandler.cs index 419c773a9f..32a83cdc4a 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ProcessOrderPaidCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ProcessOrderPaidCommandHandler.cs @@ -15,19 +15,19 @@ public class ProcessOrderPaidCommandHandler : IRequestHandler(); - await _messageProviderService.SendOrderPaidCustomerMessage(order, customer, order.CustomerLanguageId, + await _orderMessageProviderService.SendOrderPaidCustomerMessage(order, customer, order.CustomerLanguageId, orderPaidAttachmentFilePath, orderPaidAttachmentFileName, orderPaidAttachments); - await _messageProviderService.SendOrderPaidStoreOwnerMessage(order, customer, + await _orderMessageProviderService.SendOrderPaidStoreOwnerMessage(order, customer, _languageSettings.DefaultAdminLanguageId); if (order.OrderItems.Any(x => !string.IsNullOrEmpty(x.VendorId))) { var vendors = await _mediator.Send(new GetVendorsInOrderQuery { Order = order }); foreach (var vendor in vendors) - await _messageProviderService.SendOrderPaidVendorMessage(order, vendor, + await _orderMessageProviderService.SendOrderPaidVendorMessage(order, vendor, _languageSettings.DefaultAdminLanguageId); } } diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/RefundCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/RefundCommandHandler.cs index abe5eee38b..f439f4361e 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/RefundCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/RefundCommandHandler.cs @@ -16,7 +16,7 @@ public class RefundCommandHandler : IRequestHandler private readonly LanguageSettings _languageSettings; private readonly ILogger _logger; private readonly IMediator _mediator; - private readonly IMessageProviderService _messageProviderService; + private readonly IOrderMessageProviderService _orderMessageProviderService; private readonly IOrderService _orderService; private readonly IPaymentService _paymentService; private readonly IPaymentTransactionService _paymentTransactionService; @@ -26,7 +26,7 @@ public RefundCommandHandler( IPaymentService paymentService, IPaymentTransactionService paymentTransactionService, IOrderService orderService, - IMessageProviderService messageProviderService, + IOrderMessageProviderService orderMessageProviderService, ILogger logger, LanguageSettings languageSettings) { @@ -34,7 +34,7 @@ public RefundCommandHandler( _paymentService = paymentService; _paymentTransactionService = paymentTransactionService; _orderService = orderService; - _messageProviderService = messageProviderService; + _orderMessageProviderService = orderMessageProviderService; _logger = logger; _languageSettings = languageSettings; } @@ -75,11 +75,11 @@ public async Task> Handle(RefundCommand command, CancellationToken await _mediator.Send(new CheckOrderStatusCommand { Order = order }, cancellationToken); //notifications for store owner - await _messageProviderService.SendOrderRefundedStoreOwnerMessage(order, request.AmountToRefund, + await _orderMessageProviderService.SendOrderRefundedStoreOwnerMessage(order, request.AmountToRefund, _languageSettings.DefaultAdminLanguageId); //notifications for customer - await _messageProviderService.SendOrderRefundedCustomerMessage(order, request.AmountToRefund, + await _orderMessageProviderService.SendOrderRefundedCustomerMessage(order, request.AmountToRefund, order.CustomerLanguageId); //raise event diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/RefundOfflineCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/RefundOfflineCommandHandler.cs index 89b145a4fa..c2445a0b50 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/RefundOfflineCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/RefundOfflineCommandHandler.cs @@ -15,7 +15,7 @@ public class RefundOfflineCommandHandler : IRequestHandler { private readonly IMediator _mediator; - private readonly IMessageProviderService _messageProviderService; + private readonly IOrderMessageProviderService _orderMessageProviderService; private readonly IOrderService _orderService; private readonly IShipmentService _shipmentService; @@ -21,12 +21,12 @@ public DeliveryCommandHandler( IMediator mediator, IOrderService orderService, IShipmentService shipmentService, - IMessageProviderService messageProviderService) + IOrderMessageProviderService orderMessageProviderService) { _mediator = mediator; _orderService = orderService; _shipmentService = shipmentService; - _messageProviderService = messageProviderService; + _orderMessageProviderService = orderMessageProviderService; } public async Task Handle(DeliveryCommand request, CancellationToken cancellationToken) @@ -61,7 +61,7 @@ await _orderService.InsertOrderNote(new OrderNote { }); if (request.NotifyCustomer) //send email notification - await _messageProviderService.SendShipmentDeliveredCustomerMessage(request.Shipment, order); + await _orderMessageProviderService.SendShipmentDeliveredCustomerMessage(request.Shipment, order); //check order status await _mediator.Send(new CheckOrderStatusCommand { Order = order }, cancellationToken); diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Shipping/ShipCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Shipping/ShipCommandHandler.cs index 17d24fd0dd..75df0956b7 100644 --- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Shipping/ShipCommandHandler.cs +++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Shipping/ShipCommandHandler.cs @@ -13,7 +13,7 @@ namespace Grand.Business.Checkout.Commands.Handlers.Shipping; public class ShipCommandHandler : IRequestHandler { private readonly IMediator _mediator; - private readonly IMessageProviderService _messageProviderService; + private readonly IOrderMessageProviderService _orderMessageProviderService; private readonly IOrderService _orderService; private readonly IShipmentService _shipmentService; @@ -21,12 +21,12 @@ public ShipCommandHandler( IMediator mediator, IOrderService orderService, IShipmentService shipmentService, - IMessageProviderService messageProviderService) + IOrderMessageProviderService orderMessageProviderService) { _mediator = mediator; _orderService = orderService; _shipmentService = shipmentService; - _messageProviderService = messageProviderService; + _orderMessageProviderService = orderMessageProviderService; } public async Task Handle(ShipCommand request, CancellationToken cancellationToken) @@ -67,7 +67,7 @@ await _orderService.InsertOrderNote(new OrderNote { if (request.NotifyCustomer) //notify customer - await _messageProviderService.SendShipmentSentCustomerMessage(request.Shipment, order); + await _orderMessageProviderService.SendShipmentSentCustomerMessage(request.Shipment, order); //check order status await _mediator.Send(new CheckOrderStatusCommand { Order = order }, cancellationToken); diff --git a/src/Business/Grand.Business.Core/Interfaces/Messages/ICatalogMessageProviderService.cs b/src/Business/Grand.Business.Core/Interfaces/Messages/ICatalogMessageProviderService.cs new file mode 100644 index 0000000000..bf00c85c93 --- /dev/null +++ b/src/Business/Grand.Business.Core/Interfaces/Messages/ICatalogMessageProviderService.cs @@ -0,0 +1,141 @@ +using Grand.Domain.Catalog; +using Grand.Domain.Customers; +using Grand.Domain.Stores; + +namespace Grand.Business.Core.Interfaces.Messages; + +public interface ICatalogMessageProviderService +{ + /// + /// Sends "email a friend" message + /// + /// Customer instance + /// Store + /// Message language identifier + /// Product instance + /// Customer's email + /// Friend's email + /// Personal message + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendProductEmailAFriendMessage(Customer customer, Store store, string languageId, + Product product, string customerEmail, string friendsEmail, string personalMessage, DomainHost host = null); + + /// + /// Sends wishlist "email a friend" message + /// + /// Customer + /// Store + /// Message language identifier + /// Customer's email + /// Friend's email + /// Personal message + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendWishlistEmailAFriendMessage(Customer customer, Store store, string languageId, + string customerEmail, string friendsEmail, string personalMessage, DomainHost host = null); + + /// + /// Sends "email a friend" message + /// + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendProductQuestionMessage(Customer customer, Store store, string languageId, + Product product, string customerEmail, string fullName, string phone, string message, string ipaddress, + DomainHost host = null); + + /// + /// Sends a product review notification message to a store owner + /// + /// Product + /// Product review + /// Store + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendProductReviewMessage(Product product, ProductReview productReview, Store store, string languageId, + DomainHost host = null); + + /// + /// Sends a "quantity below" notification to a store owner + /// + /// Product + /// Message language identifier + /// Queued email identifier + Task SendQuantityBelowStoreOwnerMessage(Product product, string languageId); + + /// + /// Sends a "quantity below" notification to a store owner + /// + /// Product + /// Attribute combination + /// Message language identifier + /// Queued email identifier + Task SendQuantityBelowStoreOwnerMessage(Product product, ProductAttributeCombination combination, + string languageId); + + /// + /// Sends a 'Back in stock' notification message to a customer + /// + /// Customer + /// Product + /// Subscription + /// Message language identifier + /// Queued email identifier + Task SendBackInStockMessage(Customer customer, Product product, OutOfStockSubscription subscription, + string languageId); + + /// + /// Sends auction ended notification to a customer (winner) + /// + /// Auction + /// Message language identifier + /// Bid + /// Queued email identifier + Task SendAuctionWinEndedCustomerMessage(Product product, string languageId, Bid bid); + + /// + /// Sends auction ended notification to a customer (loser) + /// + /// Auction + /// Message language identifier + /// + /// Queued email identifier + Task SendAuctionEndedLostCustomerMessage(Product product, string languageId, Bid bid); + + /// + /// Sends auction ended notification to a customer (loser - bin) + /// + /// Auction + /// Customer identifier + /// Message language identifier + /// Store identifier + /// Queued email identifier + Task SendAuctionEndedBinCustomerMessage(Product product, string customerId, string languageId, string storeId); + + /// + /// Sends auction ended notification to a store owner + /// + /// Message language identifier + /// Auction + /// Bid + /// Queued email identifier + Task SendAuctionEndedStoreOwnerMessage(Product product, string languageId, Bid bid); + + /// + /// Send outbid notification to a customer + /// + /// Message language identifier + /// Product + /// Bid + /// Queued email identifier + Task SendOutBidCustomerMessage(Product product, string languageId, Bid bid); +} diff --git a/src/Business/Grand.Business.Core/Interfaces/Messages/ICmsMessageProviderService.cs b/src/Business/Grand.Business.Core/Interfaces/Messages/ICmsMessageProviderService.cs new file mode 100644 index 0000000000..4cb4539ebd --- /dev/null +++ b/src/Business/Grand.Business.Core/Interfaces/Messages/ICmsMessageProviderService.cs @@ -0,0 +1,45 @@ +using Grand.Domain.Blogs; +using Grand.Domain.Knowledgebase; +using Grand.Domain.News; +using Grand.Domain.Stores; + +namespace Grand.Business.Core.Interfaces.Messages; + +public interface ICmsMessageProviderService +{ + /// + /// Sends a blog comment notification message to a store owner + /// + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendBlogCommentMessage(BlogPost blogPost, BlogComment blogComment, string languageId, + DomainHost host = null); + + /// + /// Sends an article comment notification message to a store owner + /// + /// Article + /// Article comment + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendArticleCommentMessage(KnowledgebaseArticle article, KnowledgebaseArticleComment articleComment, + string languageId, DomainHost host = null); + + /// + /// Sends a news comment notification message to a store owner + /// + /// News item + /// News comment + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendNewsCommentMessage(NewsItem newsItem, NewsComment newsComment, string languageId, + DomainHost host = null); +} diff --git a/src/Business/Grand.Business.Core/Interfaces/Messages/ICustomerMessageProviderService.cs b/src/Business/Grand.Business.Core/Interfaces/Messages/ICustomerMessageProviderService.cs new file mode 100644 index 0000000000..edb8d64102 --- /dev/null +++ b/src/Business/Grand.Business.Core/Interfaces/Messages/ICustomerMessageProviderService.cs @@ -0,0 +1,113 @@ +using Grand.Domain.Common; +using Grand.Domain.Customers; +using Grand.Domain.Stores; + +namespace Grand.Business.Core.Interfaces.Messages; + +public interface ICustomerMessageProviderService +{ + /// + /// Sends 'New customer' notification message to a store owner + /// + /// Customer instance + /// Store + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendCustomerRegisteredMessage(Customer customer, Store store, string languageId, DomainHost host = null); + + /// + /// Sends a welcome message to a customer + /// + /// Customer instance + /// Store + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendCustomerWelcomeMessage(Customer customer, Store store, string languageId, DomainHost host = null); + + /// + /// Sends an email validation message to a customer + /// + /// Customer instance + /// Store + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendCustomerEmailValidationMessage(Customer customer, Store store, string languageId, + DomainHost host = null); + + /// + /// Sends password recovery message to a customer + /// + /// Customer instance + /// Store + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendCustomerPasswordRecoveryMessage(Customer customer, Store store, string languageId, + DomainHost host = null); + + /// + /// Sends a new customer note added notification to a customer + /// + /// Customer instance + /// Customer note + /// Store + /// Message language identifier + /// Queued email identifier + Task SendNewCustomerNoteMessage(CustomerNote customerNote, Customer customer, Store store, string languageId); + + /// + /// Send an email token validation message to a customer + /// + /// Customer instance + /// Store instance + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendCustomerEmailTokenValidationMessage(Customer customer, Store store, string languageId, + DomainHost host = null); + + /// + /// Sends a "customer delete" notification to a store owner + /// + /// Customer + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendCustomerDeleteStoreOwnerMessage(Customer customer, string languageId, DomainHost host = null); + + /// + /// Sends "contact us" message + /// + /// Customer + /// Store + /// Message language identifier + /// Sender email + /// Sender name + /// Email subject. Pass null if you want a message template subject to be used. + /// Email body + /// Attr info + /// Custom Attributes + /// ip address + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendContactUsMessage(Customer customer, Store store, string languageId, string senderEmail, + string senderName, string subject, string body, string attrInfo, IList customAttributes, + string ipaddress, DomainHost host = null); +} diff --git a/src/Business/Grand.Business.Core/Interfaces/Messages/IMerchandiseReturnMessageProviderService.cs b/src/Business/Grand.Business.Core/Interfaces/Messages/IMerchandiseReturnMessageProviderService.cs new file mode 100644 index 0000000000..1d9e7acbba --- /dev/null +++ b/src/Business/Grand.Business.Core/Interfaces/Messages/IMerchandiseReturnMessageProviderService.cs @@ -0,0 +1,53 @@ +using Grand.Domain.Orders; +using Grand.Domain.Stores; + +namespace Grand.Business.Core.Interfaces.Messages; + +public interface IMerchandiseReturnMessageProviderService +{ + /// + /// Sends 'New Merchandise Return' message to a store owner + /// + /// Merchandise return + /// Order + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendNewMerchandiseReturnStoreOwnerMessage(MerchandiseReturn merchandiseReturn, Order order, + string languageId, DomainHost host = null); + + /// + /// Sends 'Merchandise Return status changed' message to a customer + /// + /// Merchandise return + /// Order item + /// Message language identifier + /// Queued email identifier + Task SendMerchandiseReturnStatusChangedCustomerMessage(MerchandiseReturn merchandiseReturn, Order order, + string languageId); + + /// + /// Sends 'New Merchandise Return' message to a customer + /// + /// + /// + /// + /// + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendNewMerchandiseReturnCustomerMessage(MerchandiseReturn merchandiseReturn, Order order, + string languageId, DomainHost host = null); + + /// + /// Sends a new merchandise return note added notification to a customer + /// + /// Merchandise return + /// Merchandise return note + /// Order + /// Queued email identifier + Task SendNewMerchandiseReturnNoteAddedCustomerMessage(MerchandiseReturn merchandiseReturn, + MerchandiseReturnNote merchandiseReturnNote, Order order); +} diff --git a/src/Business/Grand.Business.Core/Interfaces/Messages/IMessageLookupService.cs b/src/Business/Grand.Business.Core/Interfaces/Messages/IMessageLookupService.cs new file mode 100644 index 0000000000..bd035377f8 --- /dev/null +++ b/src/Business/Grand.Business.Core/Interfaces/Messages/IMessageLookupService.cs @@ -0,0 +1,31 @@ +using Grand.Domain.Localization; +using Grand.Domain.Messages; +using Grand.Domain.Stores; + +namespace Grand.Business.Core.Interfaces.Messages; + +/// +/// Resolves what a message needs before it is sent: store, active template, email account and language +/// +public interface IMessageLookupService +{ + /// + /// Gets the store by identifier, or the first store when it does not exist + /// + Task GetStore(string storeId); + + /// + /// Gets an active message template by name, or null when it is missing or inactive + /// + Task GetMessageTemplate(string messageTemplateName, string storeId); + + /// + /// Gets the email account of the message template, falling back to the default and then to any account + /// + Task GetEmailAccountOfMessageTemplate(MessageTemplate messageTemplate, string languageId); + + /// + /// Gets the language if published, else a published language of the store, else any language + /// + Task EnsureLanguageIsActive(string languageId, string storeId); +} diff --git a/src/Business/Grand.Business.Core/Interfaces/Messages/IMessageProviderService.cs b/src/Business/Grand.Business.Core/Interfaces/Messages/IMessageProviderService.cs index f28f90038a..6f12440117 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Messages/IMessageProviderService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Messages/IMessageProviderService.cs @@ -1,623 +1,10 @@ -using Grand.Domain.Blogs; -using Grand.Domain.Catalog; -using Grand.Domain.Common; -using Grand.Domain.Customers; -using Grand.Domain.Knowledgebase; +using Grand.Domain.Common; using Grand.Domain.Messages; -using Grand.Domain.News; -using Grand.Domain.Orders; -using Grand.Domain.Shipping; -using Grand.Domain.Stores; -using Grand.Domain.Vendors; namespace Grand.Business.Core.Interfaces.Messages; public interface IMessageProviderService { - #region Customer messages - - /// - /// Sends 'New customer' notification message to a store owner - /// - /// Customer instance - /// Store - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendCustomerRegisteredMessage(Customer customer, Store store, string languageId, DomainHost host = null); - - /// - /// Sends a welcome message to a customer - /// - /// Customer instance - /// Store - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendCustomerWelcomeMessage(Customer customer, Store store, string languageId, DomainHost host = null); - - /// - /// Sends an email validation message to a customer - /// - /// Customer instance - /// Store - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendCustomerEmailValidationMessage(Customer customer, Store store, string languageId, - DomainHost host = null); - - /// - /// Sends password recovery message to a customer - /// - /// Customer instance - /// Store - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendCustomerPasswordRecoveryMessage(Customer customer, Store store, string languageId, - DomainHost host = null); - - /// - /// Sends a new customer note added notification to a customer - /// - /// Customer instance - /// Customer note - /// Store - /// Message language identifier - /// Queued email identifier - Task SendNewCustomerNoteMessage(CustomerNote customerNote, Customer customer, Store store, string languageId); - - /// - /// Send an email token validation message to a customer - /// - /// Customer instance - /// Store instance - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendCustomerEmailTokenValidationMessage(Customer customer, Store store, string languageId, - DomainHost host = null); - - #endregion - - #region Order messages - - /// - /// Sends an order placed notification to a vendor - /// - /// Order instance - /// Customer instance - /// Vendor instance - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendOrderPlacedVendorMessage(Order order, Customer customer, Vendor vendor, string languageId, - DomainHost host = null); - - /// - /// Sends an order placed notification to a store owner - /// - /// Order instance - /// Customer instance - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendOrderPlacedStoreOwnerMessage(Order order, Customer customer, string languageId, - DomainHost host = null); - - /// - /// Sends an order paid notification to a store owner - /// - /// Order instance - /// Customer instance - /// Message language identifier - /// Queued email identifier - Task SendOrderPaidStoreOwnerMessage(Order order, Customer customer, string languageId); - - /// - /// Sends an order paid notification to a customer - /// - /// Order instance - /// Customer instance - /// Message language identifier - /// Attachment file path - /// - /// Attachment file name. If specified, then this file name will be sent to a recipient. - /// Otherwise, "AttachmentFilePath" name will be used. - /// - /// Attachments ident - /// Queued email identifier - Task SendOrderPaidCustomerMessage(Order order, Customer customer, string languageId, - string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null); - - /// - /// Sends an order paid notification to a vendor - /// - /// Order instance - /// Vendor instance - /// Message language identifier - /// Queued email identifier - Task SendOrderPaidVendorMessage(Order order, Vendor vendor, string languageId); - - /// - /// Sends an order placed notification to a customer - /// - /// Order instance - /// Customer - /// Message language identifier - /// Attachment file path - /// - /// Attachment file name. If specified, then this file name will be sent to a recipient. - /// Otherwise, "AttachmentFilePath" name will be used. - /// - /// Attachments ident - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendOrderPlacedCustomerMessage(Order order, Customer customer, string languageId, - string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null, - DomainHost host = null); - - /// - /// Sends a shipment sent notification to a customer - /// - /// Shipment - /// Order - /// Queued email identifier - Task SendShipmentSentCustomerMessage(Shipment shipment, Order order); - - /// - /// Sends a shipment delivered notification to a customer - /// - /// Shipment - /// Order - /// Queued email identifier - Task SendShipmentDeliveredCustomerMessage(Shipment shipment, Order order); - - /// - /// Sends an order completed notification to a customer - /// - /// Order instance - /// Customer instance - /// Message language identifier - /// Attachment file path - /// - /// Attachment file name. If specified, then this file name will be sent to a recipient. - /// Otherwise, "AttachmentFilePath" name will be used. - /// - /// Attachments ident - /// Queued email identifier - Task SendOrderCompletedCustomerMessage(Order order, Customer customer, string languageId, - string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null); - - /// - /// Sends an order cancelled notification to a customer - /// - /// Order instance - /// Customer instance - /// Message language identifier - /// Queued email identifier - Task SendOrderCancelledCustomerMessage(Order order, Customer customer, string languageId); - - /// - /// Sends an order cancelled notification to a store owner - /// - /// Order instance - /// Customer instance - /// Message language identifier - /// Queued email identifier - Task SendOrderCancelledStoreOwnerMessage(Order order, Customer customer, string languageId); - - /// - /// Sends an order cancel notification to a vendor - /// - /// Order instance - /// Vendor instance - /// Message language identifier - /// Queued email identifier - Task SendOrderCancelledVendorMessage(Order order, Vendor vendor, string languageId); - - /// - /// Sends an order refunded notification to a store owner - /// - /// Order instance - /// Amount refunded - /// Message language identifier - /// Queued email identifier - Task SendOrderRefundedStoreOwnerMessage(Order order, double refundedAmount, string languageId); - - /// - /// Sends an order refunded notification to a customer - /// - /// Order instance - /// Amount refunded - /// Message language identifier - /// Queued email identifier - Task SendOrderRefundedCustomerMessage(Order order, double refundedAmount, string languageId); - - /// - /// Sends a new order note added notification to a customer - /// - /// Order - /// Order note - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendNewOrderNoteAddedCustomerMessage(Order order, OrderNote orderNote, DomainHost host = null); - - #endregion - - #region Newsletter messages - - /// - /// Sends a newsletter subscription activation message - /// - /// Newsletter subscription - /// Language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendNewsLetterSubscriptionActivationMessage(NewsLetterSubscription subscription, - string languageId, DomainHost host = null); - - /// - /// Sends a newsletter subscription deactivation message - /// - /// Newsletter subscription - /// Language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendNewsLetterSubscriptionDeactivationMessage(NewsLetterSubscription subscription, - string languageId, DomainHost host = null); - - #endregion - - #region Send a message to a friend, ask question - - /// - /// Sends "email a friend" message - /// - /// Customer instance - /// Store - /// Message language identifier - /// Product instance - /// Customer's email - /// Friend's email - /// Personal message - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendProductEmailAFriendMessage(Customer customer, Store store, string languageId, - Product product, string customerEmail, string friendsEmail, string personalMessage, DomainHost host = null); - - /// - /// Sends wishlist "email a friend" message - /// - /// Customer - /// Store - /// Message language identifier - /// Customer's email - /// Friend's email - /// Personal message - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendWishlistEmailAFriendMessage(Customer customer, Store store, string languageId, - string customerEmail, string friendsEmail, string personalMessage, DomainHost host = null); - - - /// - /// Sends "email a friend" message - /// - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendProductQuestionMessage(Customer customer, Store store, string languageId, - Product product, string customerEmail, string fullName, string phone, string message, string ipaddress, - DomainHost host = null); - - #endregion - - #region Merchandise returns - - /// - /// Sends 'New Merchandise Return' message to a store owner - /// - /// Merchandise return - /// Order - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendNewMerchandiseReturnStoreOwnerMessage(MerchandiseReturn merchandiseReturn, Order order, - string languageId, DomainHost host = null); - - - /// - /// Sends 'Merchandise Return status changed' message to a customer - /// - /// Merchandise return - /// Order item - /// Message language identifier - /// Queued email identifier - Task SendMerchandiseReturnStatusChangedCustomerMessage(MerchandiseReturn merchandiseReturn, Order order, - string languageId); - - /// - /// Sends 'New Merchandise Return' message to a customer - /// - /// - /// - /// - /// - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendNewMerchandiseReturnCustomerMessage(MerchandiseReturn merchandiseReturn, Order order, - string languageId, DomainHost host = null); - - /// - /// Sends a new merchandise return note added notification to a customer - /// - /// Merchandise return - /// Merchandise return note - /// Order - /// Queued email identifier - Task SendNewMerchandiseReturnNoteAddedCustomerMessage(MerchandiseReturn merchandiseReturn, - MerchandiseReturnNote merchandiseReturnNote, Order order); - - #endregion - - #region Misc - - /// - /// Sends 'New vendor account submitted' message to a store owner - /// - /// Customer - /// Vendor - /// Store - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendNewVendorAccountApplyStoreOwnerMessage(Customer customer, Vendor vendor, Store store, - string languageId, DomainHost host = null); - - - /// - /// Sends 'Vendor information change' message to a store owner - /// - /// Vendor - /// Store - /// Message language identifier - /// Queued email identifier - Task SendVendorInformationChangeMessage(Vendor vendor, Store store, string languageId); - - /// - /// Sends a product review notification message to a store owner - /// - /// Product - /// Product review - /// Store - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendProductReviewMessage(Product product, ProductReview productReview, Store store, string languageId, - DomainHost host = null); - - /// - /// Sends a vendor review notification message to a store owner - /// - /// Vendor review - /// Store - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendVendorReviewMessage(VendorReview vendorReview, Store store, string languageId, - DomainHost host = null); - - /// - /// Sends a gift voucher notification - /// - /// Gift voucher - /// Order - /// Message language identifier - /// Queued email identifier - Task SendGiftVoucherMessage(GiftVoucher giftVoucher, Order order, string languageId); - - - /// - /// Sends a "quantity below" notification to a store owner - /// - /// Product - /// Message language identifier - /// Queued email identifier - Task SendQuantityBelowStoreOwnerMessage(Product product, string languageId); - - /// - /// Sends a "quantity below" notification to a store owner - /// - /// Product - /// Attribute combination - /// Message language identifier - /// Queued email identifier - Task SendQuantityBelowStoreOwnerMessage(Product product, ProductAttributeCombination combination, - string languageId); - - /// - /// Sends a "customer delete" notification to a store owner - /// - /// Customer - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendCustomerDeleteStoreOwnerMessage(Customer customer, string languageId, DomainHost host = null); - - /// - /// Sends a blog comment notification message to a store owner - /// - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendBlogCommentMessage(BlogPost blogPost, BlogComment blogComment, string languageId, - DomainHost host = null); - - /// - /// Sends an article comment notification message to a store owner - /// - /// Article - /// Article comment - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendArticleCommentMessage(KnowledgebaseArticle article, KnowledgebaseArticleComment articleComment, - string languageId, DomainHost host = null); - - /// - /// Sends a news comment notification message to a store owner - /// - /// News item - /// News comment - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendNewsCommentMessage(NewsItem newsItem, NewsComment newsComment, string languageId, - DomainHost host = null); - - /// - /// Sends a 'Back in stock' notification message to a customer - /// - /// Customer - /// Product - /// Subscription - /// Message language identifier - /// Queued email identifier - Task SendBackInStockMessage(Customer customer, Product product, OutOfStockSubscription subscription, - string languageId); - - - /// - /// Sends "contact us" message - /// - /// Customer - /// Store - /// Message language identifier - /// Sender email - /// Sender name - /// Email subject. Pass null if you want a message template subject to be used. - /// Email body - /// Attr info - /// Custom Attributes - /// ip address - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendContactUsMessage(Customer customer, Store store, string languageId, string senderEmail, - string senderName, string subject, string body, string attrInfo, IList customAttributes, - string ipaddress, DomainHost host = null); - - /// - /// Sends "contact vendor" message - /// - /// Customer - /// Store - /// Vendor - /// Message language identifier - /// Sender email - /// Sender name - /// Email subject. Pass null if you want a message template subject to be used. - /// Email body - /// Ip Address - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - Task SendContactVendorMessage(Customer customer, Store store, Vendor vendor, string languageId, - string senderEmail, string senderName, string subject, string body, string ipaddress, DomainHost host = null); - - /// - /// Sends auction ended notification to a customer (winner) - /// - /// Auction - /// Message language identifier - /// Bid - /// Queued email identifier - Task SendAuctionWinEndedCustomerMessage(Product product, string languageId, Bid bid); - - /// - /// Sends auction ended notification to a customer (loser) - /// - /// Auction - /// Message language identifier - /// - /// Queued email identifier - Task SendAuctionEndedLostCustomerMessage(Product product, string languageId, Bid bid); - - /// - /// Sends auction ended notification to a customer (loser - bin) - /// - /// Auction - /// Customer identifier - /// Message language identifier - /// Store identifier - /// Queued email identifier - Task SendAuctionEndedBinCustomerMessage(Product product, string customerId, string languageId, string storeId); - - /// - /// Sends auction ended notification to a store owner - /// - /// Message language identifier - /// Auction - /// Bid - /// Queued email identifier - Task SendAuctionEndedStoreOwnerMessage(Product product, string languageId, Bid bid); - - /// - /// Send outbid notification to a customer - /// - /// Message language identifier - /// Product - /// Bid - /// Queued email identifier - Task SendOutBidCustomerMessage(Product product, string languageId, Bid bid); - /// /// Send notification /// @@ -646,6 +33,4 @@ Task SendNotification(MessageTemplate messageTemplate, string replyToEmailAddress = null, string replyToName = null, string fromEmail = null, string fromName = null, string subject = null, Reference reference = Reference.None, string objectId = ""); - - #endregion -} \ No newline at end of file +} diff --git a/src/Business/Grand.Business.Core/Interfaces/Messages/INewsletterMessageProviderService.cs b/src/Business/Grand.Business.Core/Interfaces/Messages/INewsletterMessageProviderService.cs new file mode 100644 index 0000000000..517fcaf3d1 --- /dev/null +++ b/src/Business/Grand.Business.Core/Interfaces/Messages/INewsletterMessageProviderService.cs @@ -0,0 +1,31 @@ +using Grand.Domain.Messages; +using Grand.Domain.Stores; + +namespace Grand.Business.Core.Interfaces.Messages; + +public interface INewsletterMessageProviderService +{ + /// + /// Sends a newsletter subscription activation message + /// + /// Newsletter subscription + /// Language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendNewsLetterSubscriptionActivationMessage(NewsLetterSubscription subscription, + string languageId, DomainHost host = null); + + /// + /// Sends a newsletter subscription deactivation message + /// + /// Newsletter subscription + /// Language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendNewsLetterSubscriptionDeactivationMessage(NewsLetterSubscription subscription, + string languageId, DomainHost host = null); +} diff --git a/src/Business/Grand.Business.Core/Interfaces/Messages/IOrderMessageProviderService.cs b/src/Business/Grand.Business.Core/Interfaces/Messages/IOrderMessageProviderService.cs new file mode 100644 index 0000000000..5734434ed9 --- /dev/null +++ b/src/Business/Grand.Business.Core/Interfaces/Messages/IOrderMessageProviderService.cs @@ -0,0 +1,188 @@ +using Grand.Domain.Customers; +using Grand.Domain.Orders; +using Grand.Domain.Shipping; +using Grand.Domain.Stores; +using Grand.Domain.Vendors; + +namespace Grand.Business.Core.Interfaces.Messages; + +public interface IOrderMessageProviderService +{ + /// + /// Sends an order placed notification to a vendor + /// + /// Order instance + /// Customer instance + /// Vendor instance + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendOrderPlacedVendorMessage(Order order, Customer customer, Vendor vendor, string languageId, + DomainHost host = null); + + /// + /// Sends an order placed notification to a store owner + /// + /// Order instance + /// Customer instance + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendOrderPlacedStoreOwnerMessage(Order order, Customer customer, string languageId, + DomainHost host = null); + + /// + /// Sends an order paid notification to a store owner + /// + /// Order instance + /// Customer instance + /// Message language identifier + /// Queued email identifier + Task SendOrderPaidStoreOwnerMessage(Order order, Customer customer, string languageId); + + /// + /// Sends an order paid notification to a customer + /// + /// Order instance + /// Customer instance + /// Message language identifier + /// Attachment file path + /// + /// Attachment file name. If specified, then this file name will be sent to a recipient. + /// Otherwise, "AttachmentFilePath" name will be used. + /// + /// Attachments ident + /// Queued email identifier + Task SendOrderPaidCustomerMessage(Order order, Customer customer, string languageId, + string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null); + + /// + /// Sends an order paid notification to a vendor + /// + /// Order instance + /// Vendor instance + /// Message language identifier + /// Queued email identifier + Task SendOrderPaidVendorMessage(Order order, Vendor vendor, string languageId); + + /// + /// Sends an order placed notification to a customer + /// + /// Order instance + /// Customer + /// Message language identifier + /// Attachment file path + /// + /// Attachment file name. If specified, then this file name will be sent to a recipient. + /// Otherwise, "AttachmentFilePath" name will be used. + /// + /// Attachments ident + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendOrderPlacedCustomerMessage(Order order, Customer customer, string languageId, + string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null, + DomainHost host = null); + + /// + /// Sends a shipment sent notification to a customer + /// + /// Shipment + /// Order + /// Queued email identifier + Task SendShipmentSentCustomerMessage(Shipment shipment, Order order); + + /// + /// Sends a shipment delivered notification to a customer + /// + /// Shipment + /// Order + /// Queued email identifier + Task SendShipmentDeliveredCustomerMessage(Shipment shipment, Order order); + + /// + /// Sends an order completed notification to a customer + /// + /// Order instance + /// Customer instance + /// Message language identifier + /// Attachment file path + /// + /// Attachment file name. If specified, then this file name will be sent to a recipient. + /// Otherwise, "AttachmentFilePath" name will be used. + /// + /// Attachments ident + /// Queued email identifier + Task SendOrderCompletedCustomerMessage(Order order, Customer customer, string languageId, + string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null); + + /// + /// Sends an order cancelled notification to a customer + /// + /// Order instance + /// Customer instance + /// Message language identifier + /// Queued email identifier + Task SendOrderCancelledCustomerMessage(Order order, Customer customer, string languageId); + + /// + /// Sends an order cancelled notification to a store owner + /// + /// Order instance + /// Customer instance + /// Message language identifier + /// Queued email identifier + Task SendOrderCancelledStoreOwnerMessage(Order order, Customer customer, string languageId); + + /// + /// Sends an order cancel notification to a vendor + /// + /// Order instance + /// Vendor instance + /// Message language identifier + /// Queued email identifier + Task SendOrderCancelledVendorMessage(Order order, Vendor vendor, string languageId); + + /// + /// Sends an order refunded notification to a store owner + /// + /// Order instance + /// Amount refunded + /// Message language identifier + /// Queued email identifier + Task SendOrderRefundedStoreOwnerMessage(Order order, double refundedAmount, string languageId); + + /// + /// Sends an order refunded notification to a customer + /// + /// Order instance + /// Amount refunded + /// Message language identifier + /// Queued email identifier + Task SendOrderRefundedCustomerMessage(Order order, double refundedAmount, string languageId); + + /// + /// Sends a new order note added notification to a customer + /// + /// Order + /// Order note + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendNewOrderNoteAddedCustomerMessage(Order order, OrderNote orderNote, DomainHost host = null); + + /// + /// Sends a gift voucher notification + /// + /// Gift voucher + /// Order + /// Message language identifier + /// Queued email identifier + Task SendGiftVoucherMessage(GiftVoucher giftVoucher, Order order, string languageId); +} diff --git a/src/Business/Grand.Business.Core/Interfaces/Messages/IVendorMessageProviderService.cs b/src/Business/Grand.Business.Core/Interfaces/Messages/IVendorMessageProviderService.cs new file mode 100644 index 0000000000..7652ae7079 --- /dev/null +++ b/src/Business/Grand.Business.Core/Interfaces/Messages/IVendorMessageProviderService.cs @@ -0,0 +1,64 @@ +using Grand.Domain.Common; +using Grand.Domain.Customers; +using Grand.Domain.Stores; +using Grand.Domain.Vendors; + +namespace Grand.Business.Core.Interfaces.Messages; + +public interface IVendorMessageProviderService +{ + /// + /// Sends 'New vendor account submitted' message to a store owner + /// + /// Customer + /// Vendor + /// Store + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendNewVendorAccountApplyStoreOwnerMessage(Customer customer, Vendor vendor, Store store, + string languageId, DomainHost host = null); + + /// + /// Sends 'Vendor information change' message to a store owner + /// + /// Vendor + /// Store + /// Message language identifier + /// Queued email identifier + Task SendVendorInformationChangeMessage(Vendor vendor, Store store, string languageId); + + /// + /// Sends a vendor review notification message to a store owner + /// + /// Vendor review + /// Store + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendVendorReviewMessage(VendorReview vendorReview, Store store, string languageId, + DomainHost host = null); + + /// + /// Sends "contact vendor" message + /// + /// Customer + /// Store + /// Vendor + /// Message language identifier + /// Sender email + /// Sender name + /// Email subject. Pass null if you want a message template subject to be used. + /// Email body + /// Ip Address + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + Task SendContactVendorMessage(Customer customer, Store store, Vendor vendor, string languageId, + string senderEmail, string senderName, string subject, string body, string ipaddress, DomainHost host = null); +} diff --git a/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendNotificationsToSubscribersCommandHandler.cs b/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendNotificationsToSubscribersCommandHandler.cs index 7babf68260..bbcc92ad6a 100644 --- a/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendNotificationsToSubscribersCommandHandler.cs +++ b/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendNotificationsToSubscribersCommandHandler.cs @@ -15,16 +15,16 @@ public class IList> { private readonly ICustomerService _customerService; - private readonly IMessageProviderService _messageProviderService; + private readonly ICatalogMessageProviderService _catalogMessageProviderService; private readonly IRepository _outOfStockSubscriptionRepository; public SendNotificationsToSubscribersCommandHandler( ICustomerService customerService, - IMessageProviderService messageProviderService, + ICatalogMessageProviderService catalogMessageProviderService, IRepository outOfStockSubscriptionRepository) { _customerService = customerService; - _messageProviderService = messageProviderService; + _catalogMessageProviderService = catalogMessageProviderService; _outOfStockSubscriptionRepository = outOfStockSubscriptionRepository; } @@ -42,7 +42,7 @@ public async Task> Handle(SendNotificationsToSubsc if (customer == null || !CommonHelper.IsValidEmail(customer.Email)) continue; var customerLanguageId = customer.GetUserFieldFromEntity(SystemCustomerFieldNames.LanguageId, subscription.StoreId); - await _messageProviderService.SendBackInStockMessage(customer, request.Product, subscription, + await _catalogMessageProviderService.SendBackInStockMessage(customer, request.Product, subscription, customerLanguageId); } diff --git a/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendOutBidCustomerNotificationCommandHandler.cs b/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendOutBidCustomerNotificationCommandHandler.cs index 599567da1a..9dddab3d2b 100644 --- a/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendOutBidCustomerNotificationCommandHandler.cs +++ b/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendOutBidCustomerNotificationCommandHandler.cs @@ -6,16 +6,16 @@ namespace Grand.Module.ScheduledTasks.Commands.Handlers.Catalog; public class SendOutBidCustomerNotificationCommandHandler : IRequestHandler { - private readonly IMessageProviderService _messageProviderService; + private readonly ICatalogMessageProviderService _catalogMessageProviderService; - public SendOutBidCustomerNotificationCommandHandler(IMessageProviderService messageProviderService) + public SendOutBidCustomerNotificationCommandHandler(ICatalogMessageProviderService catalogMessageProviderService) { - _messageProviderService = messageProviderService; + _catalogMessageProviderService = catalogMessageProviderService; } public async Task Handle(SendOutBidCustomerCommand request, CancellationToken cancellationToken) { - await _messageProviderService.SendOutBidCustomerMessage(request.Product, request.Language.Id, request.Bid); + await _catalogMessageProviderService.SendOutBidCustomerMessage(request.Product, request.Language.Id, request.Bid); return true; } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendQuantityBelowStoreOwnerNotificationCommandHandler.cs b/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendQuantityBelowStoreOwnerNotificationCommandHandler.cs index 4dc66b0afc..c148d5dfcd 100644 --- a/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendQuantityBelowStoreOwnerNotificationCommandHandler.cs +++ b/src/Business/Grand.Business.Messages/Commands/Handlers/Catalog/SendQuantityBelowStoreOwnerNotificationCommandHandler.cs @@ -9,23 +9,23 @@ public class SendQuantityBelowStoreOwnerNotificationCommandHandler : IRequestHandler { private readonly LanguageSettings _languageSettings; - private readonly IMessageProviderService _messageProviderService; + private readonly ICatalogMessageProviderService _catalogMessageProviderService; public SendQuantityBelowStoreOwnerNotificationCommandHandler( - IMessageProviderService messageProviderService, + ICatalogMessageProviderService catalogMessageProviderService, LanguageSettings languageSettings) { - _messageProviderService = messageProviderService; + _catalogMessageProviderService = catalogMessageProviderService; _languageSettings = languageSettings; } public async Task Handle(SendQuantityBelowStoreOwnerCommand request, CancellationToken cancellationToken) { if (request.ProductAttributeCombination == null) - await _messageProviderService.SendQuantityBelowStoreOwnerMessage(request.Product, + await _catalogMessageProviderService.SendQuantityBelowStoreOwnerMessage(request.Product, _languageSettings.DefaultAdminLanguageId); else - await _messageProviderService.SendQuantityBelowStoreOwnerMessage(request.Product, + await _catalogMessageProviderService.SendQuantityBelowStoreOwnerMessage(request.Product, request.ProductAttributeCombination, _languageSettings.DefaultAdminLanguageId); return true; diff --git a/src/Business/Grand.Business.Messages/DotLiquidDrops/LiquidObjectBuilderExtensions.cs b/src/Business/Grand.Business.Messages/DotLiquidDrops/LiquidObjectBuilderExtensions.cs new file mode 100644 index 0000000000..954506d11e --- /dev/null +++ b/src/Business/Grand.Business.Messages/DotLiquidDrops/LiquidObjectBuilderExtensions.cs @@ -0,0 +1,17 @@ +using Grand.Domain.Customers; +using Grand.Domain.Localization; +using Grand.Domain.Stores; + +namespace Grand.Business.Messages.DotLiquidDrops; + +internal static class LiquidObjectBuilderExtensions +{ + public static void AddCustomerTokensIfNotNull(this LiquidObjectBuilder builder, Customer customer, Store store, + DomainHost host, Language language) + { + if (customer != null) + { + builder.AddCustomerTokens(customer, store, host, language); + } + } +} diff --git a/src/Business/Grand.Business.Messages/Services/CatalogMessageProviderService.cs b/src/Business/Grand.Business.Messages/Services/CatalogMessageProviderService.cs new file mode 100644 index 0000000000..48d57714a9 --- /dev/null +++ b/src/Business/Grand.Business.Messages/Services/CatalogMessageProviderService.cs @@ -0,0 +1,608 @@ +using Grand.Business.Core.Commands.Messages.Common; +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Messages; +using Grand.Business.Core.Queries.Messages; +using Grand.Business.Messages.DotLiquidDrops; +using Grand.Domain.Catalog; +using Grand.Domain.Common; +using Grand.Domain.Customers; +using Grand.Domain.Localization; +using Grand.Domain.Messages; +using Grand.Domain.Stores; +using Grand.Mediator; + +namespace Grand.Business.Messages.Services; + +public class CatalogMessageProviderService : ICatalogMessageProviderService +{ + #region Ctor + + public CatalogMessageProviderService( + IMessageLookupService messageLookupService, + IMessageProviderService messageProviderService, + IMediator mediator, + CommonSettings commonSettings) + { + _messageLookupService = messageLookupService; + _messageProviderService = messageProviderService; + _mediator = mediator; + _commonSettings = commonSettings; + } + + #endregion + + #region Fields + + private readonly IMessageLookupService _messageLookupService; + private readonly IMessageProviderService _messageProviderService; + private readonly IMediator _mediator; + private readonly CommonSettings _commonSettings; + + #endregion + + #region Methods + + /// + /// Sends "email a friend" message + /// + /// Customer instance + /// Store + /// Message language identifier + /// Product instance + /// Customer's email + /// Friend's email + /// Personal message + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendProductEmailAFriendMessage(Customer customer, Store store, string languageId, + Product product, string customerEmail, string friendsEmail, string personalMessage, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(customer); + ArgumentNullException.ThrowIfNull(product); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate(MessageTemplateNames.SendProductEmailAFriendMessage, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddEmailAFriendTokens(personalMessage, customerEmail, friendsEmail) + .AddCustomerTokens(customer, store, host, language) + .AddProductTokens(product, language, store, host); + var liquidObject = await builder.BuildAsync(); + + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = friendsEmail; + var toName = ""; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Customer, objectId: customer.Id); + } + + /// + /// Sends wishlist "email a friend" message + /// + /// Customer + /// Store + /// Message language identifier + /// Customer's email + /// Friend's email + /// Personal message + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendWishlistEmailAFriendMessage(Customer customer, Store store, string languageId, + string customerEmail, string friendsEmail, string personalMessage, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(customer); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate(MessageTemplateNames.SendWishlistEmailAFriendMessage, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddCustomerTokens(customer, store, host, language) + .AddEmailAFriendTokens(personalMessage, customerEmail, friendsEmail); + + var liquidObject = await builder.BuildAsync(); + + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = friendsEmail; + var toName = ""; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Customer, objectId: customer.Id); + } + + /// + /// Sends "email a friend" message + /// + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendProductQuestionMessage(Customer customer, Store store, string languageId, + Product product, string customerEmail, string fullName, string phone, string message, string ipaddress, + DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(customer); + ArgumentNullException.ThrowIfNull(product); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate(MessageTemplateNames.SendProductQuestionMessage, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddCustomerTokens(customer, store, host, language) + .AddProductTokens(product, language, store, host); + var liquidObject = await builder.BuildAsync(); + liquidObject.AskQuestion = new LiquidAskQuestion(message, customerEmail, fullName, phone); + + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + //store in database + if (_commonSettings.StoreInDatabaseContactUsForm) + { + var subject = messageTemplate.GetTranslation(mt => mt.Subject, languageId); + var body = messageTemplate.GetTranslation(mt => mt.Body, languageId); + + var subjectReplaced = LiquidExtensions.Render(liquidObject, subject); + var bodyReplaced = LiquidExtensions.Render(liquidObject, body); + + await _mediator.Send(new InsertContactUsCommand { + CustomerId = customer.Id, + StoreId = store.Id, + VendorId = product.VendorId, + Email = customerEmail, + Enquiry = bodyReplaced, + FullName = fullName, + Subject = subjectReplaced, + EmailAccountId = emailAccount.Id, + RemoteIpAddress = ipaddress + }); + } + + var toEmail = emailAccount.Email; + var toName = ""; + + if (!string.IsNullOrEmpty(product.VendorId)) + { + var vendor = await _mediator.Send(new GetVendorByIdQuery { Id = product.VendorId }); + if (vendor != null) + { + toEmail = vendor.Email; + toName = vendor.Name; + } + } + + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, replyToEmailAddress: customerEmail, + reference: Reference.Customer, objectId: customer.Id); + } + + /// + /// Sends a product review notification message to a store owner + /// + /// Product + /// Product review + /// Store + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendProductReviewMessage(Product product, ProductReview productReview, + Store store, string languageId, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(productReview); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("Product.ProductReview", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = productReview.CustomerId }); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddProductReviewTokens(product, productReview); + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Product, objectId: product.Id); + } + + /// + /// Sends a "quantity below" notification to a store owner + /// + /// Product + /// Message language identifier + public virtual async Task SendQuantityBelowStoreOwnerMessage(Product product, string languageId) + { + ArgumentNullException.ThrowIfNull(product); + + var store = await _messageLookupService.GetStore(""); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("QuantityBelow.StoreOwnerNotification", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var host = store.PrimaryHost(); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddProductTokens(product, language, store, host); + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Product, objectId: product.Id); + } + + /// + /// Sends a "quantity below" notification to a store owner + /// + /// + /// Attribute combination + /// Message language identifier + public virtual async Task SendQuantityBelowStoreOwnerMessage(Product product, + ProductAttributeCombination combination, string languageId) + { + ArgumentNullException.ThrowIfNull(combination); + + var store = await _messageLookupService.GetStore(""); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = + await _messageLookupService.GetMessageTemplate("QuantityBelow.AttributeCombination.StoreOwnerNotification", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var host = store.PrimaryHost(); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddProductTokens(product, language, store, host) + .AddAttributeCombinationTokens(product, combination, language, host); + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Product, objectId: product.Id); + } + + /// + /// Sends a 'Out of stock' notification message to a customer + /// + /// Customer + /// Product + /// Subscription + /// Message language identifier + public virtual async Task SendBackInStockMessage(Customer customer, Product product, + OutOfStockSubscription subscription, string languageId) + { + ArgumentNullException.ThrowIfNull(subscription); + + var store = await _messageLookupService.GetStore(subscription.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("Customer.OutOfStock", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var host = store.PrimaryHost(); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount); + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + builder.AddOutOfStockTokens(product, subscription, store, host, language); + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = customer?.Email; + var toName = customer?.GetFullName(); + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Customer, objectId: customer?.Id); + } + + public virtual async Task SendAuctionWinEndedCustomerMessage(Product product, string languageId, Bid bid) + { + ArgumentNullException.ThrowIfNull(product); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = bid.CustomerId }); + if (customer == null) return 0; + if (string.IsNullOrEmpty(languageId)) + languageId = customer.GetUserFieldFromEntity(SystemCustomerFieldNames.LanguageId); + + var store = await _messageLookupService.GetStore(bid.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("AuctionEnded.CustomerNotificationWin", store.Id); + if (messageTemplate == null) + return 0; + + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var host = store.PrimaryHost(); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddAuctionTokens(product, bid) + .AddCustomerTokens(customer, store, host, language) + .AddProductTokens(product, language, store, host) + .AddStoreTokens(store, language, emailAccount); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = customer.Email; + var toName = customer.GetFullName(); + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Customer, objectId: customer.Id); + } + + public virtual async Task SendAuctionEndedLostCustomerMessage(Product product, string languageId, Bid bid) + { + ArgumentNullException.ThrowIfNull(product); + + var winner = await _mediator.Send(new GetCustomerByIdQuery { Id = bid.CustomerId }); + if (winner == null) return 0; + + var store = await _messageLookupService.GetStore(bid.StoreId); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("AuctionEnded.CustomerNotificationLost", store.Id); + if (messageTemplate == null) + return 0; + + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var host = store.PrimaryHost(); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddAuctionTokens(product, bid) + .AddProductTokens(product, language, store, host) + .AddStoreTokens(store, language, emailAccount); + var liquidObject = await builder.BuildAsync(); + + var bids = (await _mediator.Send(new GetBidsByProductIdQuery { ProductId = bid.ProductId })) + .Where(x => x.CustomerId != bid.CustomerId).GroupBy(x => x.CustomerId); + foreach (var item in bids) + { + var builder2 = new LiquidObjectBuilder(_mediator, liquidObject); + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = item.Key }); + if (customer == null) + continue; + + var customerLanguageId = string.IsNullOrEmpty(languageId) + ? customer.GetUserFieldFromEntity(SystemCustomerFieldNames.LanguageId) + : languageId; + builder2.AddCustomerTokens(customer, store, host, language); + var liquidObject2 = await builder2.BuildAsync(); + + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject2); + + var toEmail = customer.Email; + var toName = customer.GetFullName(); + await _messageProviderService.SendNotification(messageTemplate, emailAccount, + customerLanguageId, liquidObject2, + toEmail, toName, + reference: Reference.Customer, objectId: customer.Id); + } + + return 0; + } + + public virtual async Task SendAuctionEndedBinCustomerMessage(Product product, string customerId, + string languageId, string storeId) + { + ArgumentNullException.ThrowIfNull(product); + + var store = await _messageLookupService.GetStore(storeId); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("AuctionEnded.CustomerNotificationBin", storeId); + if (messageTemplate == null) + return 0; + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var host = store.PrimaryHost(); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddProductTokens(product, language, store, host) + .AddStoreTokens(store, language, emailAccount); + + var liquidObject = await builder.BuildAsync(); + var bids = (await _mediator.Send(new GetBidsByProductIdQuery { ProductId = product.Id })) + .Where(x => x.CustomerId != customerId).GroupBy(x => x.CustomerId); + foreach (var item in bids) + { + var builder2 = new LiquidObjectBuilder(_mediator, liquidObject); + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = item.Key }); + if (customer != null) + { + if (string.IsNullOrEmpty(languageId)) + languageId = customer.GetUserFieldFromEntity(SystemCustomerFieldNames.LanguageId); + + builder2.AddCustomerTokensIfNotNull(customer, store, host, language); + var liquidObject2 = await builder2.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject2); + + var toEmail = customer.Email; + var toName = customer.GetFullName(); + await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject2, + toEmail, toName, + reference: Reference.Customer, objectId: customer.Id); + } + } + + return 0; + } + + public virtual async Task SendAuctionEndedStoreOwnerMessage(Product product, string languageId, Bid bid) + { + ArgumentNullException.ThrowIfNull(product); + + var builder = new LiquidObjectBuilder(_mediator); + MessageTemplate messageTemplate; + EmailAccount emailAccount; + + if (bid != null) + { + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = bid.CustomerId }); + if (string.IsNullOrEmpty(languageId)) + languageId = customer?.GetUserFieldFromEntity(SystemCustomerFieldNames.LanguageId); + + var store = await _messageLookupService.GetStore(bid.StoreId); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + messageTemplate = await _messageLookupService.GetMessageTemplate("AuctionEnded.StoreOwnerNotification", store.Id); + if (messageTemplate == null) + return 0; + + emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + builder.AddAuctionTokens(product, bid) + .AddStoreTokens(store, language, emailAccount); + builder.AddCustomerTokensIfNotNull(customer, store, store.PrimaryHost(), language); + } + else + { + var store = await _messageLookupService.GetStore(""); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + messageTemplate = await _messageLookupService.GetMessageTemplate("AuctionExpired.StoreOwnerNotification", ""); + if (messageTemplate == null) + return 0; + + emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + builder.AddProductTokens(product, language, store, store.PrimaryHost()); + } + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName); + } + + /// + /// Send outbid notification to a customer + /// + /// Message language identifier + /// Product + /// + public virtual async Task SendOutBidCustomerMessage(Product product, string languageId, Bid bid) + { + ArgumentNullException.ThrowIfNull(product); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = bid.CustomerId }); + if (customer == null) + return 0; + + if (string.IsNullOrEmpty(languageId)) + languageId = customer.GetUserFieldFromEntity(SystemCustomerFieldNames.LanguageId); + + var store = await _messageLookupService.GetStore(bid.StoreId); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("BidUp.CustomerNotification", store.Id); + if (messageTemplate == null) + return 0; + + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var host = store.PrimaryHost(); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddAuctionTokens(product, bid) + .AddCustomerTokens(customer, store, host, language) + .AddStoreTokens(store, language, emailAccount); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = customer.Email; + var toName = customer.GetFullName(); + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Customer, objectId: customer.Id); + } + + #endregion +} diff --git a/src/Business/Grand.Business.Messages/Services/CmsMessageProviderService.cs b/src/Business/Grand.Business.Messages/Services/CmsMessageProviderService.cs new file mode 100644 index 0000000000..81d20695cb --- /dev/null +++ b/src/Business/Grand.Business.Messages/Services/CmsMessageProviderService.cs @@ -0,0 +1,174 @@ +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Common.Directory; +using Grand.Business.Core.Interfaces.Messages; +using Grand.Business.Core.Queries.Messages; +using Grand.Business.Messages.DotLiquidDrops; +using Grand.Domain.Blogs; +using Grand.Domain.Common; +using Grand.Domain.Customers; +using Grand.Domain.Knowledgebase; +using Grand.Domain.Localization; +using Grand.Domain.News; +using Grand.Domain.Stores; +using Grand.Mediator; + +namespace Grand.Business.Messages.Services; + +public class CmsMessageProviderService : ICmsMessageProviderService +{ + #region Ctor + + public CmsMessageProviderService( + IMessageLookupService messageLookupService, + IMessageProviderService messageProviderService, + IMediator mediator, + IGroupService groupService) + { + _messageLookupService = messageLookupService; + _messageProviderService = messageProviderService; + _mediator = mediator; + _groupService = groupService; + } + + #endregion + + #region Fields + + private readonly IMessageLookupService _messageLookupService; + private readonly IMessageProviderService _messageProviderService; + private readonly IMediator _mediator; + private readonly IGroupService _groupService; + + #endregion + + #region Methods + + /// + /// Sends a blog comment notification message to a store owner + /// + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendBlogCommentMessage(BlogPost blogPost, BlogComment blogComment, string languageId, + DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(blogComment); + + var store = await _messageLookupService.GetStore(blogComment.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("Blog.BlogComment", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddBlogCommentTokens(blogPost, blogComment, store, host, language); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = blogComment.CustomerId }); + if (customer != null && await _groupService.IsRegistered(customer)) + builder.AddCustomerTokens(customer, store, host, language); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Blog, objectId: blogPost.Id); + } + + /// + /// Sends an article comment notification message to a store owner + /// + /// + /// Article comment + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendArticleCommentMessage(KnowledgebaseArticle article, + KnowledgebaseArticleComment articleComment, string languageId, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(articleComment); + + var store = await _messageLookupService.GetStore(""); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("Knowledgebase.ArticleComment", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddArticleCommentTokens(article, articleComment, store, host, language); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = articleComment.CustomerId }); + if (customer != null && await _groupService.IsRegistered(customer)) + builder.AddCustomerTokens(customer, store, host, language); + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName); + } + + /// + /// Sends a news comment notification message to a store owner + /// + /// News item + /// News comment + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendNewsCommentMessage(NewsItem newsItem, NewsComment newsComment, string languageId, + DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(newsComment); + + var store = await _messageLookupService.GetStore(newsComment.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("News.NewsComment", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddNewsCommentTokens(newsItem, newsComment, store, host, language); + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = newsComment.CustomerId }); + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName); + } + + #endregion +} diff --git a/src/Business/Grand.Business.Messages/Services/CustomerMessageProviderService.cs b/src/Business/Grand.Business.Messages/Services/CustomerMessageProviderService.cs new file mode 100644 index 0000000000..51d4160b25 --- /dev/null +++ b/src/Business/Grand.Business.Messages/Services/CustomerMessageProviderService.cs @@ -0,0 +1,303 @@ +using Grand.Business.Core.Commands.Messages.Common; +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Messages; +using Grand.Business.Messages.DotLiquidDrops; +using Grand.Domain.Common; +using Grand.Domain.Customers; +using Grand.Domain.Messages; +using Grand.Domain.Stores; +using Grand.Mediator; +using Grand.SharedKernel.Extensions; +using System.Net; + +namespace Grand.Business.Messages.Services; + +public class CustomerMessageProviderService : ICustomerMessageProviderService +{ + #region Ctor + + public CustomerMessageProviderService( + IMessageLookupService messageLookupService, + IMessageProviderService messageProviderService, + IMediator mediator, + CommonSettings commonSettings) + { + _messageLookupService = messageLookupService; + _messageProviderService = messageProviderService; + _mediator = mediator; + _commonSettings = commonSettings; + } + + #endregion + + #region Fields + + private readonly IMessageLookupService _messageLookupService; + private readonly IMessageProviderService _messageProviderService; + private readonly IMediator _mediator; + private readonly CommonSettings _commonSettings; + + #endregion + + #region Methods + + /// + /// Send a message to a customer + /// + /// Customer instance + /// Store + /// Message language identifier + /// Message template name + /// Send email to email account + /// Customer note + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + protected virtual async Task SendCustomerMessage(Customer customer, Store store, string languageId, + string templateName, bool toEmailAccount = false, CustomerNote customerNote = null, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(customer); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate(templateName, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddCustomerTokens(customer, store, host, language, customerNote); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = toEmailAccount ? emailAccount.Email : customer.Email; + var toName = toEmailAccount ? emailAccount.DisplayName : customer.GetFullName(); + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, reference: Reference.Customer, objectId: customer.Id); + } + + /// + /// Sends 'New customer' notification message to a store owner + /// + /// Customer instance + /// Store identifier + /// Message language identifier + /// Queued email identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendCustomerRegisteredMessage(Customer customer, Store store, string languageId, + DomainHost host = null) + { + return await SendCustomerMessage(customer, store, languageId, MessageTemplateNames.CustomerRegistered, true, + host: host); + } + + /// + /// Sends a welcome message to a customer + /// + /// Customer instance + /// Store + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendCustomerWelcomeMessage(Customer customer, Store store, string languageId, + DomainHost host = null) + { + return await SendCustomerMessage(customer, store, languageId, MessageTemplateNames.CustomerWelcome, host: host); + } + + /// + /// Sends an email validation message to a customer + /// + /// Customer instance + /// Store + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendCustomerEmailValidationMessage(Customer customer, Store store, string languageId, + DomainHost host = null) + { + return await SendCustomerMessage(customer, store, languageId, MessageTemplateNames.CustomerEmailValidation, + host: host); + } + + /// + /// Sends password recovery message to a customer + /// + /// Customer instance + /// Store + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendCustomerPasswordRecoveryMessage(Customer customer, Store store, + string languageId, DomainHost host = null) + { + return await SendCustomerMessage(customer, store, languageId, MessageTemplateNames.CustomerPasswordRecovery, + host: host); + } + + /// + /// Sends a new customer note added notification to a customer + /// + /// Customer note + /// Customer + /// Store + /// Message language identifier + /// Queued email identifier + public virtual async Task SendNewCustomerNoteMessage(CustomerNote customerNote, Customer customer, Store store, + string languageId) + { + return await SendCustomerMessage(customer, store, languageId, MessageTemplateNames.CustomerNewCustomerNote, + customerNote: customerNote); + } + + /// + /// Send an email token validation message to a customer + /// + /// Customer instance + /// Store instance + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendCustomerEmailTokenValidationMessage(Customer customer, Store store, + string languageId, DomainHost host = null) + { + return await SendCustomerMessage(customer, store, languageId, + MessageTemplateNames.CustomerEmailTokenValidationMessage, host: host); + } + + /// + /// Sends a "new VAT" notification to a store owner + /// + /// Customer + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendCustomerDeleteStoreOwnerMessage(Customer customer, string languageId, + DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(customer); + + var store = await _messageLookupService.GetStore(customer.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("CustomerDelete.StoreOwnerNotification", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddCustomerTokens(customer, store, host, language); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Customer, objectId: customer.Id); + } + + /// + /// Sends "contact us" message + /// + /// Customer + /// Store + /// Message language identifier + /// Sender email + /// Sender name + /// Email subject. Pass null if you want a message template subject to be used. + /// Email body + /// Attr info + /// CustomAttributes + /// Ip address + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendContactUsMessage(Customer customer, Store store, string languageId, + string senderEmail, + string senderName, string subject, string body, string attrInfo, IList customAttributes, + string ipaddress, DomainHost host = null) + { + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + var messageTemplate = await _messageLookupService.GetMessageTemplate("Service.ContactUs", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + string fromEmail; + string fromName; + senderName = WebUtility.HtmlEncode(senderName); + senderEmail = WebUtility.HtmlEncode(senderEmail); + //required for some SMTP servers + if (_commonSettings.UseSystemEmailForContactUsForm) + { + fromEmail = emailAccount.Email; + fromName = emailAccount.DisplayName; + } + else + { + fromEmail = senderEmail; + fromName = senderName; + } + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddCustomerTokens(customer, store, host, language); + + var liquidObject = await builder.BuildAsync(); + liquidObject.ContactUs = new LiquidContactUs(senderEmail, senderName, body, attrInfo); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + + //store in database + if (_commonSettings.StoreInDatabaseContactUsForm) + await _mediator.Send(new InsertContactUsCommand { + CustomerId = customer.Id, + StoreId = store.Id, + VendorId = "", + Email = senderEmail, + Enquiry = body, + FullName = senderName, + Subject = string.IsNullOrEmpty(subject) ? "Contact Us (form)" : subject, + ContactAttributeDescription = attrInfo, + ContactAttributes = customAttributes, + EmailAccountId = emailAccount.Id, + RemoteIpAddress = ipaddress + }); + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, languageId, liquidObject, toEmail, toName, + fromEmail: fromEmail, + fromName: fromName, + subject: subject, + replyToEmailAddress: senderEmail, + replyToName: senderName, reference: Reference.Customer, objectId: customer?.Id); + } + + #endregion +} diff --git a/src/Business/Grand.Business.Messages/Services/MerchandiseReturnMessageProviderService.cs b/src/Business/Grand.Business.Messages/Services/MerchandiseReturnMessageProviderService.cs new file mode 100644 index 0000000000..f666de2e66 --- /dev/null +++ b/src/Business/Grand.Business.Messages/Services/MerchandiseReturnMessageProviderService.cs @@ -0,0 +1,234 @@ +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Messages; +using Grand.Business.Core.Queries.Messages; +using Grand.Business.Messages.DotLiquidDrops; +using Grand.Domain.Common; +using Grand.Domain.Customers; +using Grand.Domain.Localization; +using Grand.Domain.Orders; +using Grand.Domain.Stores; +using Grand.Mediator; + +namespace Grand.Business.Messages.Services; + +public class MerchandiseReturnMessageProviderService : IMerchandiseReturnMessageProviderService +{ + #region Ctor + + public MerchandiseReturnMessageProviderService( + IMessageLookupService messageLookupService, + IMessageProviderService messageProviderService, + IMediator mediator) + { + _messageLookupService = messageLookupService; + _messageProviderService = messageProviderService; + _mediator = mediator; + } + + #endregion + + #region Fields + + private readonly IMessageLookupService _messageLookupService; + private readonly IMessageProviderService _messageProviderService; + private readonly IMediator _mediator; + + #endregion + + #region Methods + + /// + /// Sends 'New Merchandise Return' message to a store owner + /// + /// Merchandise return + /// Order + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendNewMerchandiseReturnStoreOwnerMessage(MerchandiseReturn merchandiseReturn, + Order order, string languageId, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(merchandiseReturn); + + var store = await _messageLookupService.GetStore(order.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = + await _messageLookupService.GetMessageTemplate(MessageTemplateNames.SendNewMerchandiseReturnStoreOwnerMessage, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = merchandiseReturn.CustomerId }); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount); + + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + builder.AddMerchandiseReturnTokens(merchandiseReturn, store, host, order, language); + + var liquidObject = await builder.BuildAsync(); + + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + if (!string.IsNullOrEmpty(merchandiseReturn.VendorId)) + { + var vendor = await _mediator.Send(new GetVendorByIdQuery { Id = merchandiseReturn.VendorId }); + if (vendor != null) + { + var vendorEmail = vendor.Email; + var vendorName = vendor.Name; + await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + vendorEmail, vendorName); + } + } + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.MerchandiseReturn, objectId: merchandiseReturn.Id); + } + + /// + /// Sends 'Merchandise Return status changed' message to a customer + /// + /// Merchandise return + /// Order + /// Message language identifier + public virtual async Task SendMerchandiseReturnStatusChangedCustomerMessage( + MerchandiseReturn merchandiseReturn, Order order, string languageId) + { + ArgumentNullException.ThrowIfNull(merchandiseReturn); + + var store = await _messageLookupService.GetStore(order.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = + await _messageLookupService.GetMessageTemplate(MessageTemplateNames.SendMerchandiseReturnStatusChangedCustomerMessage, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = merchandiseReturn.CustomerId }); + + var host = store.PrimaryHost(); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount); + + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + builder.AddMerchandiseReturnTokens(merchandiseReturn, store, host, order, language); + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.Email : customer.Email; + var toName = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.FirstName : customer.GetFullName(); + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.MerchandiseReturn, objectId: merchandiseReturn.Id); + } + + /// + /// Sends 'New Merchandise Return' message to a customer + /// + /// Merchandise return + /// Order + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendNewMerchandiseReturnCustomerMessage(MerchandiseReturn merchandiseReturn, + Order order, string languageId, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(merchandiseReturn); + + var store = await _messageLookupService.GetStore(order.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = + await _messageLookupService.GetMessageTemplate(MessageTemplateNames.SendNewMerchandiseReturnCustomerMessage, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = merchandiseReturn.CustomerId }); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount); + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + builder.AddMerchandiseReturnTokens(merchandiseReturn, store, host, order, language); + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.Email : customer.Email; + var toName = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.FirstName : customer.GetFullName(); + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.MerchandiseReturn, objectId: merchandiseReturn.Id); + } + + /// + /// Sends a new merchandise return note added notification to a customer + /// + /// Merchandise return + /// Merchandise return note + /// Order + public virtual async Task SendNewMerchandiseReturnNoteAddedCustomerMessage(MerchandiseReturn merchandiseReturn, + MerchandiseReturnNote merchandiseReturnNote, Order order) + { + ArgumentNullException.ThrowIfNull(merchandiseReturnNote); + ArgumentNullException.ThrowIfNull(merchandiseReturn); + + var store = await _messageLookupService.GetStore(order.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(order.CustomerLanguageId, store.Id); + + var messageTemplate = + await _messageLookupService.GetMessageTemplate(MessageTemplateNames.SendNewMerchandiseReturnNoteAddedCustomerMessage, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = merchandiseReturn.CustomerId }); + + var host = store.PrimaryHost(); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount); + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + builder.AddMerchandiseReturnTokens(merchandiseReturn, store, host, order, language, + merchandiseReturnNote); + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.Email : customer.Email; + var toName = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.FirstName : customer.GetFullName(); + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + language.Id, liquidObject, + toEmail, toName, + reference: Reference.MerchandiseReturn, objectId: merchandiseReturn.Id); + } + + #endregion +} diff --git a/src/Business/Grand.Business.Messages/Services/MessageLookupService.cs b/src/Business/Grand.Business.Messages/Services/MessageLookupService.cs new file mode 100644 index 0000000000..1e63326c14 --- /dev/null +++ b/src/Business/Grand.Business.Messages/Services/MessageLookupService.cs @@ -0,0 +1,89 @@ +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Interfaces.Common.Stores; +using Grand.Business.Core.Interfaces.Messages; +using Grand.Domain.Localization; +using Grand.Domain.Messages; +using Grand.Domain.Stores; + +namespace Grand.Business.Messages.Services; + +public class MessageLookupService : IMessageLookupService +{ + #region Ctor + + public MessageLookupService( + IMessageTemplateService messageTemplateService, + ILanguageService languageService, + IEmailAccountService emailAccountService, + IStoreService storeService, + EmailAccountSettings emailAccountSettings) + { + _messageTemplateService = messageTemplateService; + _languageService = languageService; + _emailAccountService = emailAccountService; + _storeService = storeService; + _emailAccountSettings = emailAccountSettings; + } + + #endregion + + #region Fields + + private readonly IMessageTemplateService _messageTemplateService; + private readonly ILanguageService _languageService; + private readonly IEmailAccountService _emailAccountService; + private readonly IStoreService _storeService; + private readonly EmailAccountSettings _emailAccountSettings; + + #endregion + + #region Methods + + public virtual async Task GetStore(string storeId) + { + return await _storeService.GetStoreById(storeId) ?? (await _storeService.GetAllStores()).FirstOrDefault(); + } + + public virtual async Task GetMessageTemplate(string messageTemplateName, string storeId) + { + var messageTemplate = await _messageTemplateService.GetMessageTemplateByName(messageTemplateName, storeId); + + //no template found + if (messageTemplate == null) + return null; + + //ensure it's active + var isActive = messageTemplate.IsActive; + return !isActive ? null : messageTemplate; + } + + public virtual async Task GetEmailAccountOfMessageTemplate(MessageTemplate messageTemplate, + string languageId) + { + var emailAccounId = messageTemplate.GetTranslation(mt => mt.EmailAccountId, languageId); + var emailAccount = await _emailAccountService.GetEmailAccountById(emailAccounId); + emailAccount ??= await _emailAccountService.GetEmailAccountById(_emailAccountSettings.DefaultEmailAccountId); + emailAccount ??= (await _emailAccountService.GetAllEmailAccounts()).FirstOrDefault(); + return emailAccount; + } + + public virtual async Task EnsureLanguageIsActive(string languageId, string storeId) + { + //load language by specified ID + var language = await _languageService.GetLanguageById(languageId); + + if (language is not { Published: true }) + //load any language from the specified store + language = (await _languageService.GetAllLanguages(storeId: storeId)).FirstOrDefault(); + if (language is not { Published: true }) + //load any language + language = (await _languageService.GetAllLanguages()).FirstOrDefault(); + + if (language == null) + throw new Exception("No active language could be loaded"); + return language; + } + + #endregion +} diff --git a/src/Business/Grand.Business.Messages/Services/MessageProviderService.cs b/src/Business/Grand.Business.Messages/Services/MessageProviderService.cs index ec09d5c108..c707833e1f 100644 --- a/src/Business/Grand.Business.Messages/Services/MessageProviderService.cs +++ b/src/Business/Grand.Business.Messages/Services/MessageProviderService.cs @@ -1,26 +1,9 @@ -using Grand.Business.Core.Commands.Messages.Common; -using Grand.Business.Core.Extensions; -using Grand.Business.Core.Interfaces.Common.Directory; -using Grand.Business.Core.Interfaces.Common.Localization; -using Grand.Business.Core.Interfaces.Common.Stores; +using Grand.Business.Core.Extensions; using Grand.Business.Core.Interfaces.Messages; -using Grand.Business.Core.Queries.Messages; using Grand.Business.Messages.DotLiquidDrops; -using Grand.Domain.Blogs; -using Grand.Domain.Catalog; using Grand.Domain.Common; -using Grand.Domain.Customers; -using Grand.Domain.Knowledgebase; -using Grand.Domain.Localization; using Grand.Domain.Messages; -using Grand.Domain.News; -using Grand.Domain.Orders; -using Grand.Domain.Shipping; -using Grand.Domain.Stores; -using Grand.Domain.Vendors; -using Grand.Mediator; using Grand.SharedKernel.Extensions; -using System.Net; namespace Grand.Business.Messages.Services; @@ -28,2016 +11,21 @@ public class MessageProviderService : IMessageProviderService { #region Ctor - public MessageProviderService( - IMessageTemplateService messageTemplateService, - IQueuedEmailService queuedEmailService, - ILanguageService languageService, - IEmailAccountService emailAccountService, - IStoreService storeService, - IGroupService groupService, - IMediator mediator, - EmailAccountSettings emailAccountSettings, - CommonSettings commonSettings) + public MessageProviderService(IQueuedEmailService queuedEmailService) { - _messageTemplateService = messageTemplateService; _queuedEmailService = queuedEmailService; - _languageService = languageService; - _emailAccountService = emailAccountService; - _storeService = storeService; - _groupService = groupService; - _emailAccountSettings = emailAccountSettings; - _commonSettings = commonSettings; - _mediator = mediator; } #endregion #region Fields - private readonly IMessageTemplateService _messageTemplateService; - private readonly IQueuedEmailService _queuedEmailService; - private readonly ILanguageService _languageService; - private readonly IEmailAccountService _emailAccountService; - private readonly IStoreService _storeService; - private readonly IGroupService _groupService; - private readonly IMediator _mediator; - - private readonly EmailAccountSettings _emailAccountSettings; - private readonly CommonSettings _commonSettings; - - #endregion - - #region Utilities - - protected virtual async Task GetStore(string storeId) - { - return await _storeService.GetStoreById(storeId) ?? (await _storeService.GetAllStores()).FirstOrDefault(); - } - - protected virtual async Task GetMessageTemplate(string messageTemplateName, string storeId) - { - var messageTemplate = await _messageTemplateService.GetMessageTemplateByName(messageTemplateName, storeId); - - //no template found - if (messageTemplate == null) - return null; - - //ensure it's active - var isActive = messageTemplate.IsActive; - return !isActive ? null : messageTemplate; - } - - protected virtual async Task GetEmailAccountOfMessageTemplate(MessageTemplate messageTemplate, - string languageId) - { - var emailAccounId = messageTemplate.GetTranslation(mt => mt.EmailAccountId, languageId); - var emailAccount = await _emailAccountService.GetEmailAccountById(emailAccounId); - emailAccount ??= await _emailAccountService.GetEmailAccountById(_emailAccountSettings.DefaultEmailAccountId); - emailAccount ??= (await _emailAccountService.GetAllEmailAccounts()).FirstOrDefault(); - return emailAccount; - } - protected virtual async Task EnsureLanguageIsActive(string languageId, string storeId) - { - //load language by specified ID - var language = await _languageService.GetLanguageById(languageId); - - if (language is not { Published: true }) - //load any language from the specified store - language = (await _languageService.GetAllLanguages(storeId: storeId)).FirstOrDefault(); - if (language is not { Published: true }) - //load any language - language = (await _languageService.GetAllLanguages()).FirstOrDefault(); - - if (language == null) - throw new Exception("No active language could be loaded"); - return language; - } - - private void AddCustomerTokensIfNotNull(LiquidObjectBuilder builder, Customer customer, Store store, - DomainHost host, Language language) - { - if (customer != null) - { - builder.AddCustomerTokens(customer, store, host, language); - } - } + private readonly IQueuedEmailService _queuedEmailService; #endregion #region Methods - #region Customer messages - - /// - /// Send a message to a customer - /// - /// Customer instance - /// Store - /// Message language identifier - /// Message template name - /// Send email to email account - /// Customer note - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - protected virtual async Task SendCustomerMessage(Customer customer, Store store, string languageId, - string templateName, bool toEmailAccount = false, CustomerNote customerNote = null, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(customer); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate(templateName, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddCustomerTokens(customer, store, host, language, customerNote); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = toEmailAccount ? emailAccount.Email : customer.Email; - var toName = toEmailAccount ? emailAccount.DisplayName : customer.GetFullName(); - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, reference: Reference.Customer, objectId: customer.Id); - } - - /// - /// Sends 'New customer' notification message to a store owner - /// - /// Customer instance - /// Store identifier - /// Message language identifier - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendCustomerRegisteredMessage(Customer customer, Store store, string languageId, - DomainHost host = null) - { - return await SendCustomerMessage(customer, store, languageId, MessageTemplateNames.CustomerRegistered, true, - host: host); - } - - /// - /// Sends a welcome message to a customer - /// - /// Customer instance - /// Store - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendCustomerWelcomeMessage(Customer customer, Store store, string languageId, - DomainHost host = null) - { - return await SendCustomerMessage(customer, store, languageId, MessageTemplateNames.CustomerWelcome, host: host); - } - - /// - /// Sends an email validation message to a customer - /// - /// Customer instance - /// Store - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendCustomerEmailValidationMessage(Customer customer, Store store, string languageId, - DomainHost host = null) - { - return await SendCustomerMessage(customer, store, languageId, MessageTemplateNames.CustomerEmailValidation, - host: host); - } - - /// - /// Sends password recovery message to a customer - /// - /// Customer instance - /// Store - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendCustomerPasswordRecoveryMessage(Customer customer, Store store, - string languageId, DomainHost host = null) - { - return await SendCustomerMessage(customer, store, languageId, MessageTemplateNames.CustomerPasswordRecovery, - host: host); - } - - /// - /// Sends a new customer note added notification to a customer - /// - /// Customer note - /// Customer - /// Store - /// Message language identifier - /// Queued email identifier - public virtual async Task SendNewCustomerNoteMessage(CustomerNote customerNote, Customer customer, Store store, - string languageId) - { - return await SendCustomerMessage(customer, store, languageId, MessageTemplateNames.CustomerNewCustomerNote, - customerNote: customerNote); - } - - /// - /// Send an email token validation message to a customer - /// - /// Customer instance - /// Store instance - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendCustomerEmailTokenValidationMessage(Customer customer, Store store, - string languageId, DomainHost host = null) - { - return await SendCustomerMessage(customer, store, languageId, - MessageTemplateNames.CustomerEmailTokenValidationMessage, host: host); - } - - #endregion - - #region Order messages - - /// - /// Sends an order placed notification to a store owner - /// - /// Order instance - /// Customer - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendOrderPlacedStoreOwnerMessage(Order order, Customer customer, string languageId, - DomainHost host = null) - { - return await SendOrderStoreOwnerMessage(MessageTemplateNames.SendOrderPlacedStoreOwnerMessage, order, customer, - languageId, host: host); - } - - /// - /// Sends an order paid notification to a store owner - /// - /// Order instance - /// Customer instance - /// Message language identifier - public virtual async Task SendOrderPaidStoreOwnerMessage(Order order, Customer customer, string languageId) - { - return await SendOrderStoreOwnerMessage(MessageTemplateNames.SendOrderPaidStoreOwnerMessage, order, customer, - languageId); - } - - /// - /// Sends an order cancelled notification to an admin - /// - /// Order instance - /// Customer instance - /// Message language identifier - /// Queued email identifier - public virtual async Task SendOrderCancelledStoreOwnerMessage(Order order, Customer customer, - string languageId) - { - return await SendOrderStoreOwnerMessage(MessageTemplateNames.SendOrderCancelledStoreOwnerMessage, order, - customer, languageId); - } - - /// Sends an order refunded notification to a store owner - /// Order instance - /// Amount refunded - /// Message language identifier - public virtual async Task SendOrderRefundedStoreOwnerMessage(Order order, double refundedAmount, - string languageId) - { - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = order.CustomerId }); - return await SendOrderStoreOwnerMessage(MessageTemplateNames.SendOrderRefundedStoreOwnerMessage, order, - customer, languageId, refundedAmount); - } - - /// - /// Sends an order notification to a store owner - /// - /// Message template - /// Order instance - /// Customer instance - /// Message language identifier - /// - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - private async Task SendOrderStoreOwnerMessage(string template, Order order, Customer customer, - string languageId, double refundedAmount = 0, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(order); - - var store = await GetStore(order.StoreId); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate(template, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - host = store.LinkHost(host); - var liquidBuilder = new LiquidObjectBuilder(_mediator); - liquidBuilder.AddStoreTokens(store, language, emailAccount) - .AddOrderTokens(order, customer, store, host); - AddCustomerTokensIfNotNull(liquidBuilder, customer, store, host, language); - - var liquidObject = await liquidBuilder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Order, objectId: order.Id); - } - - /// - /// Sends an order placed notification to a customer - /// - /// Order instance - /// - /// Message language identifier - /// Attachment file path - /// - /// Attachment file name. If specified, then this file name will be sent to a recipient. - /// Otherwise, "AttachmentFilePath" name will be used. - /// - /// Attachments - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendOrderPlacedCustomerMessage(Order order, Customer customer, string languageId, - string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null, - DomainHost host = null) - { - return await SendOrderCustomerMessage(MessageTemplateNames.SendOrderPlacedCustomerMessage, order, customer, - languageId, attachmentFilePath, attachmentFileName, attachments, host: host); - } - - /// - /// Sends an order paid notification to a customer - /// - /// Order instance - /// Customer instance - /// Message language identifier - /// Attachment file path - /// - /// Attachment file name. If specified, then this file name will be sent to a recipient. - /// Otherwise, "AttachmentFilePath" name will be used. - /// - /// Attachments ident - public virtual async Task SendOrderPaidCustomerMessage(Order order, Customer customer, string languageId, - string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null) - { - return await SendOrderCustomerMessage(MessageTemplateNames.SendOrderPaidCustomerMessage, order, customer, - languageId, attachmentFilePath, attachmentFileName, attachments); - } - - /// - /// Sends an order completed notification to a customer - /// - /// Order instance - /// Customer instance - /// Message language identifier - /// Attachment file path - /// - /// Attachment file name. If specified, then this file name will be sent to a recipient. - /// Otherwise, "AttachmentFilePath" name will be used. - /// - /// Attachments ident - public virtual async Task SendOrderCompletedCustomerMessage(Order order, Customer customer, string languageId, - string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null) - { - return await SendOrderCustomerMessage(MessageTemplateNames.SendOrderCompletedCustomerMessage, order, customer, - languageId, attachmentFilePath, attachmentFileName, attachments); - } - - /// - /// Sends an order cancelled notification to a customer - /// - /// Order instance - /// Customer instance - /// Message language identifier - public virtual async Task SendOrderCancelledCustomerMessage(Order order, Customer customer, string languageId) - { - return await SendOrderCustomerMessage(MessageTemplateNames.SendOrderCancelledCustomerMessage, order, customer, - languageId); - } - - /// - /// Sends an order refunded notification to a customer - /// - /// Order instance - /// Amount refunded - /// Message language identifier - /// Queued email identifier - public virtual async Task SendOrderRefundedCustomerMessage(Order order, double refundedAmount, - string languageId) - { - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = order.CustomerId }); - return await SendOrderCustomerMessage(MessageTemplateNames.SendOrderRefundedCustomerMessage, order, customer, - languageId, refundedAmount: refundedAmount); - } - - /// - /// Sends an order notification to a customer - /// - /// Message template - /// Order instance - /// Customer instance - /// Message language identifier - /// Attachment file path - /// - /// Attachment file name. If specified, then this file name will be sent to a recipient. - /// Otherwise, "AttachmentFilePath" name will be used. - /// - /// Attachments ident - /// - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - private async Task SendOrderCustomerMessage(string message, Order order, Customer customer, string languageId, - string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null, - double refundedAmount = 0, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(order); - - var store = await GetStore(order.StoreId); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate(message, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddOrderTokens(order, customer, store, host); - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = order.BillingAddress.Email; - var toName = $"{order.BillingAddress.FirstName} {order.BillingAddress.LastName}"; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - attachmentFilePath, - attachmentFileName, - attachments, - reference: Reference.Order, objectId: order.Id); - } - - /// - /// Sends an order placed notification to a vendor - /// - /// Order instance - /// Customer instance - /// Vendor instance - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendOrderPlacedVendorMessage(Order order, Customer customer, Vendor vendor, - string languageId, DomainHost host = null) - { - return await SendOrderVendorMessage(MessageTemplateNames.SendOrderPlacedVendorMessage, order, vendor, - languageId, host: host); - } - - /// - /// Sends an order paid notification to a vendor - /// - /// Order instance - /// Vendor instance - /// Message language identifier - public virtual async Task SendOrderPaidVendorMessage(Order order, Vendor vendor, string languageId) - { - return await SendOrderVendorMessage(MessageTemplateNames.SendOrderPaidVendorMessage, order, vendor, languageId); - } - - /// - /// Sends an order cancel notification to a vendor - /// - /// Order instance - /// Vendor instance - /// Message language identifier - public virtual async Task SendOrderCancelledVendorMessage(Order order, Vendor vendor, string languageId) - { - return await SendOrderVendorMessage(MessageTemplateNames.SendOrderCancelledVendorMessage, order, vendor, - languageId); - } - - /// - /// Sends an order notification to a vendor - /// - /// Message template - /// Order instance - /// Vendor instance - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - private async Task SendOrderVendorMessage(string message, Order order, Vendor vendor, string languageId, - DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(order); - ArgumentNullException.ThrowIfNull(vendor); - - var store = await GetStore(order.StoreId); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate(message, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = order.CustomerId }); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddOrderTokens(order, customer, store, host, vendor: vendor); - - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = vendor.Email; - var toName = vendor.Name; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Order, objectId: order.Id); - } - - - /// - /// Sends a shipment sent notification to a customer - /// - /// Shipment - /// Order - public virtual async Task SendShipmentSentCustomerMessage(Shipment shipment, Order order) - { - return await SendShipmentCustomerMessage(MessageTemplateNames.SendShipmentSentCustomerMessage, shipment, order); - } - - /// - /// Sends a shipment delivered notification to a customer - /// - /// Shipment - /// Order - public virtual async Task SendShipmentDeliveredCustomerMessage(Shipment shipment, Order order) - { - return await SendShipmentCustomerMessage(MessageTemplateNames.SendShipmentDeliveredCustomerMessage, shipment, - order); - } - - /// - /// Send a shipment notification to a customer - /// - /// Message template - /// Shipment - /// Order - private async Task SendShipmentCustomerMessage(string message, Shipment shipment, Order order) - { - ArgumentNullException.ThrowIfNull(shipment); - - if (order == null) - throw new Exception("Order cannot be loaded"); - - var store = await GetStore(order.StoreId); - var language = await EnsureLanguageIsActive(order.CustomerLanguageId, store.Id); - - var messageTemplate = await GetMessageTemplate(message, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = order.CustomerId }); - - var host = store.PrimaryHost(); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddShipmentTokens(shipment, order, store, host, language) - .AddOrderTokens(order, customer, store, host); - - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = order.BillingAddress.Email; - var toName = $"{order.BillingAddress.FirstName} {order.BillingAddress.LastName}"; - return await SendNotification(messageTemplate, emailAccount, - language.Id, liquidObject, - toEmail, toName, - reference: Reference.Shipment, objectId: shipment.Id); - } - - - /// - /// Sends a new order note added notification to a customer - /// - /// Order - /// Order note - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendNewOrderNoteAddedCustomerMessage(Order order, OrderNote orderNote, - DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(orderNote); - ArgumentNullException.ThrowIfNull(order); - - var store = await GetStore(order.StoreId); - var language = await EnsureLanguageIsActive(order.CustomerLanguageId, store.Id); - - var messageTemplate = - await GetMessageTemplate(MessageTemplateNames.SendNewOrderNoteAddedCustomerMessage, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = order.CustomerId }); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddOrderTokens(order, customer, store, host, orderNote); - - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = order.BillingAddress.Email; - var toName = $"{order.BillingAddress.FirstName} {order.BillingAddress.LastName}"; - return await SendNotification(messageTemplate, emailAccount, - language.Id, liquidObject, - toEmail, toName, - reference: Reference.Order, objectId: order.Id); - } - - #endregion - - #region Newsletter messages - - /// - /// Sends a newsletter subscription activation message - /// - /// Newsletter subscription - /// Language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendNewsLetterSubscriptionActivationMessage(NewsLetterSubscription subscription, - string languageId, DomainHost host = null) - { - return await SendNewsLetterSubscriptionMessage(MessageTemplateNames.SendNewsLetterSubscriptionActivationMessage, - subscription, languageId, host: host); - } - - /// - /// Sends a newsletter subscription deactivation message - /// - /// Newsletter subscription - /// Language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendNewsLetterSubscriptionDeactivationMessage(NewsLetterSubscription subscription, - string languageId, DomainHost host = null) - { - return await SendNewsLetterSubscriptionMessage( - MessageTemplateNames.SendNewsLetterSubscriptionDeactivationMessage, subscription, languageId, host: host); - } - - /// - /// Send a newsletter subscription message - /// - /// Message template - /// Newsletter subscription - /// Language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - private async Task SendNewsLetterSubscriptionMessage(string message, NewsLetterSubscription subscription, - string languageId, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(subscription); - - var store = await GetStore(subscription.StoreId); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate(message, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddNewsLetterSubscriptionTokens(subscription, store, host); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = subscription.Email; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, string.Empty, - reference: Reference.Customer, objectId: subscription.CustomerId); - } - - #endregion - - #region Send a message to a friend, ask question - - /// - /// Sends "email a friend" message - /// - /// Customer instance - /// Store - /// Message language identifier - /// Product instance - /// Customer's email - /// Friend's email - /// Personal message - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendProductEmailAFriendMessage(Customer customer, Store store, string languageId, - Product product, string customerEmail, string friendsEmail, string personalMessage, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(customer); - ArgumentNullException.ThrowIfNull(product); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate(MessageTemplateNames.SendProductEmailAFriendMessage, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddEmailAFriendTokens(personalMessage, customerEmail, friendsEmail) - .AddCustomerTokens(customer, store, host, language) - .AddProductTokens(product, language, store, host); - var liquidObject = await builder.BuildAsync(); - - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = friendsEmail; - var toName = ""; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Customer, objectId: customer.Id); - } - - /// - /// Sends wishlist "email a friend" message - /// - /// Customer - /// Store - /// Message language identifier - /// Customer's email - /// Friend's email - /// Personal message - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendWishlistEmailAFriendMessage(Customer customer, Store store, string languageId, - string customerEmail, string friendsEmail, string personalMessage, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(customer); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate(MessageTemplateNames.SendWishlistEmailAFriendMessage, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddCustomerTokens(customer, store, host, language) - .AddEmailAFriendTokens(personalMessage, customerEmail, friendsEmail); - - var liquidObject = await builder.BuildAsync(); - - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = friendsEmail; - var toName = ""; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Customer, objectId: customer.Id); - } - - - /// - /// Sends "email a friend" message - /// - /// Queued email identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendProductQuestionMessage(Customer customer, Store store, string languageId, - Product product, string customerEmail, string fullName, string phone, string message, string ipaddress, - DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(customer); - ArgumentNullException.ThrowIfNull(product); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate(MessageTemplateNames.SendProductQuestionMessage, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddCustomerTokens(customer, store, host, language) - .AddProductTokens(product, language, store, host); - var liquidObject = await builder.BuildAsync(); - liquidObject.AskQuestion = new LiquidAskQuestion(message, customerEmail, fullName, phone); - - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - //store in database - if (_commonSettings.StoreInDatabaseContactUsForm) - { - var subject = messageTemplate.GetTranslation(mt => mt.Subject, languageId); - var body = messageTemplate.GetTranslation(mt => mt.Body, languageId); - - var subjectReplaced = LiquidExtensions.Render(liquidObject, subject); - var bodyReplaced = LiquidExtensions.Render(liquidObject, body); - - await _mediator.Send(new InsertContactUsCommand { - CustomerId = customer.Id, - StoreId = store.Id, - VendorId = product.VendorId, - Email = customerEmail, - Enquiry = bodyReplaced, - FullName = fullName, - Subject = subjectReplaced, - EmailAccountId = emailAccount.Id, - RemoteIpAddress = ipaddress - }); - } - - var toEmail = emailAccount.Email; - var toName = ""; - - if (!string.IsNullOrEmpty(product.VendorId)) - { - var vendor = await _mediator.Send(new GetVendorByIdQuery { Id = product.VendorId }); - if (vendor != null) - { - toEmail = vendor.Email; - toName = vendor.Name; - } - } - - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, replyToEmailAddress: customerEmail, - reference: Reference.Customer, objectId: customer.Id); - } - - #endregion - - #region Merchandise returns - - /// - /// Sends 'New Merchandise Return' message to a store owner - /// - /// Merchandise return - /// Order - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendNewMerchandiseReturnStoreOwnerMessage(MerchandiseReturn merchandiseReturn, - Order order, string languageId, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(merchandiseReturn); - - var store = await GetStore(order.StoreId); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = - await GetMessageTemplate(MessageTemplateNames.SendNewMerchandiseReturnStoreOwnerMessage, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = merchandiseReturn.CustomerId }); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount); - - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - builder.AddMerchandiseReturnTokens(merchandiseReturn, store, host, order, language); - - var liquidObject = await builder.BuildAsync(); - - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - if (!string.IsNullOrEmpty(merchandiseReturn.VendorId)) - { - var vendor = await _mediator.Send(new GetVendorByIdQuery { Id = merchandiseReturn.VendorId }); - if (vendor != null) - { - var vendorEmail = vendor.Email; - var vendorName = vendor.Name; - await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - vendorEmail, vendorName); - } - } - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.MerchandiseReturn, objectId: merchandiseReturn.Id); - } - - /// - /// Sends 'Merchandise Return status changed' message to a customer - /// - /// Merchandise return - /// Order - /// Message language identifier - public virtual async Task SendMerchandiseReturnStatusChangedCustomerMessage( - MerchandiseReturn merchandiseReturn, Order order, string languageId) - { - ArgumentNullException.ThrowIfNull(merchandiseReturn); - - var store = await GetStore(order.StoreId); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = - await GetMessageTemplate(MessageTemplateNames.SendMerchandiseReturnStatusChangedCustomerMessage, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = merchandiseReturn.CustomerId }); - - var host = store.PrimaryHost(); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount); - - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - builder.AddMerchandiseReturnTokens(merchandiseReturn, store, host, order, language); - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.Email : customer.Email; - var toName = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.FirstName : customer.GetFullName(); - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.MerchandiseReturn, objectId: merchandiseReturn.Id); - } - - /// - /// Sends 'New Merchandise Return' message to a customer - /// - /// Merchandise return - /// Order - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendNewMerchandiseReturnCustomerMessage(MerchandiseReturn merchandiseReturn, - Order order, string languageId, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(merchandiseReturn); - - var store = await GetStore(order.StoreId); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = - await GetMessageTemplate(MessageTemplateNames.SendNewMerchandiseReturnCustomerMessage, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = merchandiseReturn.CustomerId }); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount); - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - builder.AddMerchandiseReturnTokens(merchandiseReturn, store, host, order, language); - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.Email : customer.Email; - var toName = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.FirstName : customer.GetFullName(); - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.MerchandiseReturn, objectId: merchandiseReturn.Id); - } - - /// - /// Sends a new merchandise return note added notification to a customer - /// - /// Merchandise return - /// Merchandise return note - /// Order - public virtual async Task SendNewMerchandiseReturnNoteAddedCustomerMessage(MerchandiseReturn merchandiseReturn, - MerchandiseReturnNote merchandiseReturnNote, Order order) - { - ArgumentNullException.ThrowIfNull(merchandiseReturnNote); - ArgumentNullException.ThrowIfNull(merchandiseReturn); - - var store = await GetStore(order.StoreId); - var language = await EnsureLanguageIsActive(order.CustomerLanguageId, store.Id); - - var messageTemplate = - await GetMessageTemplate(MessageTemplateNames.SendNewMerchandiseReturnNoteAddedCustomerMessage, store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = merchandiseReturn.CustomerId }); - - var host = store.PrimaryHost(); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount); - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - builder.AddMerchandiseReturnTokens(merchandiseReturn, store, host, order, language, - merchandiseReturnNote); - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.Email : customer.Email; - var toName = string.IsNullOrEmpty(customer?.Email) ? order.BillingAddress.FirstName : customer.GetFullName(); - return await SendNotification(messageTemplate, emailAccount, - language.Id, liquidObject, - toEmail, toName, - reference: Reference.MerchandiseReturn, objectId: merchandiseReturn.Id); - } - - #endregion - - #region Misc - - /// - /// Sends 'New vendor account submitted' message to a store owner - /// - /// Customer - /// Vendor - /// Store - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendNewVendorAccountApplyStoreOwnerMessage(Customer customer, Vendor vendor, - Store store, string languageId, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(customer); - ArgumentNullException.ThrowIfNull(vendor); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("VendorAccountApply.StoreOwnerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator).AddStoreTokens(store, language, emailAccount) - .AddCustomerTokens(customer, store, host, language) - .AddVendorTokens(vendor, language); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Customer, objectId: customer.Id); - } - - /// - /// Sends 'Vendor information changed' message to a store owner - /// - /// Vendor - /// Store - /// Message language identifier - public virtual async Task SendVendorInformationChangeMessage(Vendor vendor, Store store, string languageId) - { - ArgumentNullException.ThrowIfNull(vendor); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("VendorInformationChange.StoreOwnerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddVendorTokens(vendor, language); - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - return await SendNotification(messageTemplate, emailAccount, languageId, liquidObject, toEmail, toName, - reference: Reference.Vendor, objectId: vendor.Id); - } - - - /// - /// Sends a gift voucher notification - /// - /// Gift voucher - /// Order - /// Message language identifier - public virtual async Task SendGiftVoucherMessage(GiftVoucher giftVoucher, Order order, string languageId) - { - ArgumentNullException.ThrowIfNull(giftVoucher); - - Store store = null; - if (order != null) store = await _storeService.GetStoreById(order.StoreId); - store ??= (await _storeService.GetAllStores()).FirstOrDefault(); - - var language = await EnsureLanguageIsActive(languageId, store?.Id); - - var messageTemplate = await GetMessageTemplate("GiftVoucher.Notification", store?.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddGiftVoucherTokens(giftVoucher, language); - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - var toEmail = giftVoucher.RecipientEmail; - var toName = giftVoucher.RecipientName; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName); - } - - /// - /// Sends a product review notification message to a store owner - /// - /// Product - /// Product review - /// Store - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendProductReviewMessage(Product product, ProductReview productReview, - Store store, string languageId, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(productReview); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("Product.ProductReview", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = productReview.CustomerId }); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddProductReviewTokens(product, productReview); - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Product, objectId: product.Id); - } - - - /// - /// Sends a vendor review notification message to a store owner - /// - /// Vendor review - /// Store - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendVendorReviewMessage(VendorReview vendorReview, Store store, - string languageId, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(vendorReview); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("Vendor.VendorReview", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - //customer - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = vendorReview.CustomerId }); - //vendor - var vendor = await _mediator.Send(new GetVendorByIdQuery { Id = vendorReview.VendorId }); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddVendorReviewTokens(vendor, vendorReview); - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - builder.AddVendorTokens(vendor, language); - var liquidObject = await builder.BuildAsync(); - - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = vendor.Email; - var toName = vendor.Name; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Customer, objectId: customer?.Id); - } - - /// - /// Sends a "quantity below" notification to a store owner - /// - /// Product - /// Message language identifier - public virtual async Task SendQuantityBelowStoreOwnerMessage(Product product, string languageId) - { - ArgumentNullException.ThrowIfNull(product); - - var store = await GetStore(""); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("QuantityBelow.StoreOwnerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var host = store.PrimaryHost(); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddProductTokens(product, language, store, host); - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Product, objectId: product.Id); - } - - /// - /// Sends a "quantity below" notification to a store owner - /// - /// - /// Attribute combination - /// Message language identifier - public virtual async Task SendQuantityBelowStoreOwnerMessage(Product product, - ProductAttributeCombination combination, string languageId) - { - ArgumentNullException.ThrowIfNull(combination); - - var store = await GetStore(""); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = - await GetMessageTemplate("QuantityBelow.AttributeCombination.StoreOwnerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var host = store.PrimaryHost(); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddProductTokens(product, language, store, host) - .AddAttributeCombinationTokens(product, combination, language, host); - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Product, objectId: product.Id); - } - - /// - /// Sends a "new VAT" notification to a store owner - /// - /// Customer - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendCustomerDeleteStoreOwnerMessage(Customer customer, string languageId, - DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(customer); - - var store = await GetStore(""); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("CustomerDelete.StoreOwnerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddCustomerTokens(customer, store, host, language); - - var liquidObject = await builder.BuildAsync(); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Customer, objectId: customer.Id); - } - - /// - /// Sends a blog comment notification message to a store owner - /// - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendBlogCommentMessage(BlogPost blogPost, BlogComment blogComment, string languageId, - DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(blogComment); - - var store = await GetStore(blogComment.StoreId); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("Blog.BlogComment", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddBlogCommentTokens(blogPost, blogComment, store, host, language); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = blogComment.CustomerId }); - if (customer != null && await _groupService.IsRegistered(customer)) - builder.AddCustomerTokens(customer, store, host, language); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Blog, objectId: blogPost.Id); - } - - /// - /// Sends an article comment notification message to a store owner - /// - /// - /// Article comment - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendArticleCommentMessage(KnowledgebaseArticle article, - KnowledgebaseArticleComment articleComment, string languageId, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(articleComment); - - var store = await GetStore(""); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("Knowledgebase.ArticleComment", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddArticleCommentTokens(article, articleComment, store, host, language); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = articleComment.CustomerId }); - if (customer != null && await _groupService.IsRegistered(customer)) - builder.AddCustomerTokens(customer, store, host, language); - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName); - } - - /// - /// Sends a news comment notification message to a store owner - /// - /// News item - /// News comment - /// Message language identifier - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendNewsCommentMessage(NewsItem newsItem, NewsComment newsComment, string languageId, - DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(newsComment); - - var store = await GetStore(newsComment.StoreId); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("News.NewsComment", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddNewsCommentTokens(newsItem, newsComment, store, host, language); - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = newsComment.CustomerId }); - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName); - } - - /// - /// Sends a 'Out of stock' notification message to a customer - /// - /// Customer - /// Product - /// Subscription - /// Message language identifier - public virtual async Task SendBackInStockMessage(Customer customer, Product product, - OutOfStockSubscription subscription, string languageId) - { - ArgumentNullException.ThrowIfNull(subscription); - - var store = await GetStore(subscription.StoreId); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("Customer.OutOfStock", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var host = store.PrimaryHost(); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount); - AddCustomerTokensIfNotNull(builder, customer, store, host, language); - - builder.AddOutOfStockTokens(product, subscription, store, host, language); - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = customer?.Email; - var toName = customer.GetFullName(); - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Customer, objectId: customer?.Id); - } - - /// - /// Sends "contact us" message - /// - /// Customer - /// Store - /// Message language identifier - /// Sender email - /// Sender name - /// Email subject. Pass null if you want a message template subject to be used. - /// Email body - /// Attr info - /// CustomAttributes - /// Ip address - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendContactUsMessage(Customer customer, Store store, string languageId, - string senderEmail, - string senderName, string subject, string body, string attrInfo, IList customAttributes, - string ipaddress, DomainHost host = null) - { - var language = await EnsureLanguageIsActive(languageId, store.Id); - var messageTemplate = await GetMessageTemplate("Service.ContactUs", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - string fromEmail; - string fromName; - senderName = WebUtility.HtmlEncode(senderName); - senderEmail = WebUtility.HtmlEncode(senderEmail); - //required for some SMTP servers - if (_commonSettings.UseSystemEmailForContactUsForm) - { - fromEmail = emailAccount.Email; - fromName = emailAccount.DisplayName; - } - else - { - fromEmail = senderEmail; - fromName = senderName; - } - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddCustomerTokens(customer, store, host, language); - - var liquidObject = await builder.BuildAsync(); - liquidObject.ContactUs = new LiquidContactUs(senderEmail, senderName, body, attrInfo); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - //store in database - if (_commonSettings.StoreInDatabaseContactUsForm) - await _mediator.Send(new InsertContactUsCommand { - CustomerId = customer.Id, - StoreId = store.Id, - VendorId = "", - Email = senderEmail, - Enquiry = body, - FullName = senderName, - Subject = string.IsNullOrEmpty(subject) ? "Contact Us (form)" : subject, - ContactAttributeDescription = attrInfo, - ContactAttributes = customAttributes, - EmailAccountId = emailAccount.Id, - RemoteIpAddress = ipaddress - }); - return await SendNotification(messageTemplate, emailAccount, languageId, liquidObject, toEmail, toName, - fromEmail: fromEmail, - fromName: fromName, - subject: subject, - replyToEmailAddress: senderEmail, - replyToName: senderName, reference: Reference.Customer, objectId: customer?.Id); - } - - /// - /// Sends "contact vendor" message - /// - /// Customer - /// Store - /// Vendor - /// Message language identifier - /// Sender email - /// Sender name - /// Email subject. Pass null if you want a message template subject to be used. - /// Email body - /// Ip address - /// - /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain - /// - public virtual async Task SendContactVendorMessage(Customer customer, Store store, Vendor vendor, - string languageId, string senderEmail, - string senderName, string subject, string body, string ipaddress, DomainHost host = null) - { - ArgumentNullException.ThrowIfNull(vendor); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("Service.ContactVendor", store.Id); - if (messageTemplate == null) - return 0; - - //email account - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - string fromEmail; - string fromName; - senderName = WebUtility.HtmlEncode(senderName); - senderEmail = WebUtility.HtmlEncode(senderEmail); - - //required for some SMTP servers - if (_commonSettings.UseSystemEmailForContactUsForm) - { - fromEmail = emailAccount.Email; - fromName = emailAccount.DisplayName; - } - else - { - fromEmail = senderEmail; - fromName = senderName; - } - - host = store.LinkHost(host); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddStoreTokens(store, language, emailAccount) - .AddCustomerTokens(customer, store, host, language); - var liquidObject = await builder.BuildAsync(); - liquidObject.ContactUs = new LiquidContactUs(senderEmail, senderName, body, ""); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = vendor.Email; - var toName = vendor.Name; - - //store in database - if (_commonSettings.StoreInDatabaseContactUsForm) - await _mediator.Send(new InsertContactUsCommand { - CustomerId = customer.Id, - StoreId = store.Id, - VendorId = vendor.Id, - Email = senderEmail, - Enquiry = body, - RemoteIpAddress = ipaddress, - FullName = senderName, - Subject = string.IsNullOrEmpty(subject) ? "Contact Us (form)" : subject, - EmailAccountId = emailAccount.Id - }); - - return await SendNotification(messageTemplate, emailAccount, languageId, liquidObject, toEmail, toName, - fromEmail: fromEmail, - fromName: fromName, - subject: subject, - replyToEmailAddress: senderEmail, - replyToName: senderName, - reference: Reference.Vendor, objectId: vendor.Id); - } - - #region Auction notification - - public virtual async Task SendAuctionWinEndedCustomerMessage(Product product, string languageId, Bid bid) - { - ArgumentNullException.ThrowIfNull(product); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = bid.CustomerId }); - if (customer == null) return 0; - if (string.IsNullOrEmpty(languageId)) - languageId = customer.GetUserFieldFromEntity(SystemCustomerFieldNames.LanguageId); - - var store = await GetStore(bid.StoreId); - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("AuctionEnded.CustomerNotificationWin", store.Id); - if (messageTemplate == null) - return 0; - - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var host = store.PrimaryHost(); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddAuctionTokens(product, bid) - .AddCustomerTokens(customer, store, host, language) - .AddProductTokens(product, language, store, host) - .AddStoreTokens(store, language, emailAccount); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = customer.Email; - var toName = customer.GetFullName(); - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Customer, objectId: customer.Id); - } - - public virtual async Task SendAuctionEndedLostCustomerMessage(Product product, string languageId, Bid bid) - { - ArgumentNullException.ThrowIfNull(product); - - var winner = await _mediator.Send(new GetCustomerByIdQuery { Id = bid.CustomerId }); - if (winner == null) return 0; - - var store = await GetStore(bid.StoreId); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("AuctionEnded.CustomerNotificationLost", store.Id); - if (messageTemplate == null) - return 0; - - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var host = store.PrimaryHost(); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddAuctionTokens(product, bid) - .AddProductTokens(product, language, store, host) - .AddStoreTokens(store, language, emailAccount); - var liquidObject = await builder.BuildAsync(); - - var bids = (await _mediator.Send(new GetBidsByProductIdQuery { ProductId = bid.ProductId })) - .Where(x => x.CustomerId != bid.CustomerId).GroupBy(x => x.CustomerId); - foreach (var item in bids) - { - var builder2 = new LiquidObjectBuilder(_mediator, liquidObject); - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = item.Key }); - if (string.IsNullOrEmpty(languageId)) - languageId = customer.GetUserFieldFromEntity(SystemCustomerFieldNames.LanguageId); - builder2.AddCustomerTokens(customer, store, host, language); - var liquidObject2 = await builder2.BuildAsync(); - - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject2); - - var toEmail = customer.Email; - var toName = customer.GetFullName(); - await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject2, - toEmail, toName, - reference: Reference.Customer, objectId: customer.Id); - } - - return 0; - } - - public virtual async Task SendAuctionEndedBinCustomerMessage(Product product, string customerId, - string languageId, string storeId) - { - ArgumentNullException.ThrowIfNull(product); - - var store = await GetStore(storeId); - - var messageTemplate = await GetMessageTemplate("AuctionEnded.CustomerNotificationBin", storeId); - if (messageTemplate == null) - return 0; - - var language = await EnsureLanguageIsActive(languageId, store.Id); - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var host = store.PrimaryHost(); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddProductTokens(product, language, store, host) - .AddStoreTokens(store, language, emailAccount); - - var liquidObject = await builder.BuildAsync(); - var bids = (await _mediator.Send(new GetBidsByProductIdQuery { ProductId = product.Id })) - .Where(x => x.CustomerId != customerId).GroupBy(x => x.CustomerId); - foreach (var item in bids) - { - var builder2 = new LiquidObjectBuilder(_mediator, liquidObject); - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = item.Key }); - if (customer != null) - { - if (string.IsNullOrEmpty(languageId)) - languageId = customer.GetUserFieldFromEntity(SystemCustomerFieldNames.LanguageId); - - AddCustomerTokensIfNotNull(builder2, customer, store, host, language); - var liquidObject2 = await builder2.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject2); - - var toEmail = customer.Email; - var toName = customer.GetFullName(); - await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject2, - toEmail, toName, - reference: Reference.Customer, objectId: customer.Id); - } - } - - return 0; - } - - public virtual async Task SendAuctionEndedStoreOwnerMessage(Product product, string languageId, Bid bid) - { - ArgumentNullException.ThrowIfNull(product); - - var builder = new LiquidObjectBuilder(_mediator); - MessageTemplate messageTemplate; - EmailAccount emailAccount; - - if (bid != null) - { - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = bid.CustomerId }); - if (string.IsNullOrEmpty(languageId)) - languageId = customer.GetUserFieldFromEntity(SystemCustomerFieldNames.LanguageId); - - var store = await GetStore(bid.StoreId); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - messageTemplate = await GetMessageTemplate("AuctionEnded.StoreOwnerNotification", store.Id); - if (messageTemplate == null) - return 0; - - emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - builder.AddAuctionTokens(product, bid) - .AddCustomerTokens(customer, store, store.PrimaryHost(), language) - .AddStoreTokens(store, language, emailAccount); - } - else - { - var store = (await _storeService.GetAllStores()).FirstOrDefault(); - var language = await EnsureLanguageIsActive(languageId, store?.Id); - messageTemplate = await GetMessageTemplate("AuctionExpired.StoreOwnerNotification", ""); - if (messageTemplate == null) - return 0; - - emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - builder.AddProductTokens(product, language, store, store.PrimaryHost()); - } - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName); - } - - - /// - /// Send outbid notification to a customer - /// - /// Message language identifier - /// Product - /// - public virtual async Task SendOutBidCustomerMessage(Product product, string languageId, Bid bid) - { - ArgumentNullException.ThrowIfNull(product); - - var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = bid.CustomerId }); - if (string.IsNullOrEmpty(languageId)) - languageId = customer.GetUserFieldFromEntity(SystemCustomerFieldNames.LanguageId); - - var store = await GetStore(bid.StoreId); - - var language = await EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = await GetMessageTemplate("BidUp.CustomerNotification", store.Id); - if (messageTemplate == null) - return 0; - - var emailAccount = await GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - - var host = store.PrimaryHost(); - var builder = new LiquidObjectBuilder(_mediator); - builder.AddAuctionTokens(product, bid) - .AddCustomerTokens(customer, store, host, language) - .AddStoreTokens(store, language, emailAccount); - - var liquidObject = await builder.BuildAsync(); - //event notification - await _mediator.MessageTokensAdded(messageTemplate, liquidObject); - - var toEmail = customer.Email; - var toName = customer.GetFullName(); - return await SendNotification(messageTemplate, emailAccount, - languageId, liquidObject, - toEmail, toName, - reference: Reference.Customer, objectId: customer.Id); - } - - #endregion - public virtual async Task SendNotification(MessageTemplate messageTemplate, EmailAccount emailAccount, string languageId, LiquidObject liquidObject, string toEmailAddress, string toName, @@ -2099,6 +87,4 @@ public virtual async Task SendNotification(MessageTemplate messageTemplate, } #endregion - - #endregion -} \ No newline at end of file +} diff --git a/src/Business/Grand.Business.Messages/Services/NewsletterMessageProviderService.cs b/src/Business/Grand.Business.Messages/Services/NewsletterMessageProviderService.cs new file mode 100644 index 0000000000..8307761605 --- /dev/null +++ b/src/Business/Grand.Business.Messages/Services/NewsletterMessageProviderService.cs @@ -0,0 +1,110 @@ +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Messages; +using Grand.Business.Messages.DotLiquidDrops; +using Grand.Domain.Common; +using Grand.Domain.Customers; +using Grand.Domain.Localization; +using Grand.Domain.Messages; +using Grand.Domain.Stores; +using Grand.Mediator; + +namespace Grand.Business.Messages.Services; + +public class NewsletterMessageProviderService : INewsletterMessageProviderService +{ + #region Ctor + + public NewsletterMessageProviderService( + IMessageLookupService messageLookupService, + IMessageProviderService messageProviderService, + IMediator mediator) + { + _messageLookupService = messageLookupService; + _messageProviderService = messageProviderService; + _mediator = mediator; + } + + #endregion + + #region Fields + + private readonly IMessageLookupService _messageLookupService; + private readonly IMessageProviderService _messageProviderService; + private readonly IMediator _mediator; + + #endregion + + #region Methods + + /// + /// Sends a newsletter subscription activation message + /// + /// Newsletter subscription + /// Language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendNewsLetterSubscriptionActivationMessage(NewsLetterSubscription subscription, + string languageId, DomainHost host = null) + { + return await SendNewsLetterSubscriptionMessage(MessageTemplateNames.SendNewsLetterSubscriptionActivationMessage, + subscription, languageId, host: host); + } + + /// + /// Sends a newsletter subscription deactivation message + /// + /// Newsletter subscription + /// Language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendNewsLetterSubscriptionDeactivationMessage(NewsLetterSubscription subscription, + string languageId, DomainHost host = null) + { + return await SendNewsLetterSubscriptionMessage( + MessageTemplateNames.SendNewsLetterSubscriptionDeactivationMessage, subscription, languageId, host: host); + } + + /// + /// Send a newsletter subscription message + /// + /// Message template + /// Newsletter subscription + /// Language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + private async Task SendNewsLetterSubscriptionMessage(string message, NewsLetterSubscription subscription, + string languageId, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(subscription); + + var store = await _messageLookupService.GetStore(subscription.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate(message, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddNewsLetterSubscriptionTokens(subscription, store, host); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = subscription.Email; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, string.Empty, + reference: Reference.Customer, objectId: subscription.CustomerId); + } + + #endregion +} diff --git a/src/Business/Grand.Business.Messages/Services/OrderMessageProviderService.cs b/src/Business/Grand.Business.Messages/Services/OrderMessageProviderService.cs new file mode 100644 index 0000000000..d8d3db74bf --- /dev/null +++ b/src/Business/Grand.Business.Messages/Services/OrderMessageProviderService.cs @@ -0,0 +1,516 @@ +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Messages; +using Grand.Business.Core.Queries.Messages; +using Grand.Business.Messages.DotLiquidDrops; +using Grand.Domain.Common; +using Grand.Domain.Customers; +using Grand.Domain.Localization; +using Grand.Domain.Orders; +using Grand.Domain.Shipping; +using Grand.Domain.Stores; +using Grand.Domain.Vendors; +using Grand.Mediator; + +namespace Grand.Business.Messages.Services; + +public class OrderMessageProviderService : IOrderMessageProviderService +{ + #region Ctor + + public OrderMessageProviderService( + IMessageLookupService messageLookupService, + IMessageProviderService messageProviderService, + IMediator mediator) + { + _messageLookupService = messageLookupService; + _messageProviderService = messageProviderService; + _mediator = mediator; + } + + #endregion + + #region Fields + + private readonly IMessageLookupService _messageLookupService; + private readonly IMessageProviderService _messageProviderService; + private readonly IMediator _mediator; + + #endregion + + #region Methods + + /// + /// Sends an order placed notification to a store owner + /// + /// Order instance + /// Customer + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendOrderPlacedStoreOwnerMessage(Order order, Customer customer, string languageId, + DomainHost host = null) + { + return await SendOrderStoreOwnerMessage(MessageTemplateNames.SendOrderPlacedStoreOwnerMessage, order, customer, + languageId, host: host); + } + + /// + /// Sends an order paid notification to a store owner + /// + /// Order instance + /// Customer instance + /// Message language identifier + public virtual async Task SendOrderPaidStoreOwnerMessage(Order order, Customer customer, string languageId) + { + return await SendOrderStoreOwnerMessage(MessageTemplateNames.SendOrderPaidStoreOwnerMessage, order, customer, + languageId); + } + + /// + /// Sends an order cancelled notification to an admin + /// + /// Order instance + /// Customer instance + /// Message language identifier + /// Queued email identifier + public virtual async Task SendOrderCancelledStoreOwnerMessage(Order order, Customer customer, + string languageId) + { + return await SendOrderStoreOwnerMessage(MessageTemplateNames.SendOrderCancelledStoreOwnerMessage, order, + customer, languageId); + } + + /// Sends an order refunded notification to a store owner + /// Order instance + /// Amount refunded + /// Message language identifier + public virtual async Task SendOrderRefundedStoreOwnerMessage(Order order, double refundedAmount, + string languageId) + { + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = order.CustomerId }); + return await SendOrderStoreOwnerMessage(MessageTemplateNames.SendOrderRefundedStoreOwnerMessage, order, + customer, languageId, refundedAmount); + } + + /// + /// Sends an order notification to a store owner + /// + /// Message template + /// Order instance + /// Customer instance + /// Message language identifier + /// + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + private async Task SendOrderStoreOwnerMessage(string template, Order order, Customer customer, + string languageId, double refundedAmount = 0, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(order); + + var store = await _messageLookupService.GetStore(order.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate(template, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + host = store.LinkHost(host); + var liquidBuilder = new LiquidObjectBuilder(_mediator); + liquidBuilder.AddStoreTokens(store, language, emailAccount) + .AddOrderTokens(order, customer, store, host); + liquidBuilder.AddCustomerTokensIfNotNull(customer, store, host, language); + + var liquidObject = await liquidBuilder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Order, objectId: order.Id); + } + + /// + /// Sends an order placed notification to a customer + /// + /// Order instance + /// + /// Message language identifier + /// Attachment file path + /// + /// Attachment file name. If specified, then this file name will be sent to a recipient. + /// Otherwise, "AttachmentFilePath" name will be used. + /// + /// Attachments + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendOrderPlacedCustomerMessage(Order order, Customer customer, string languageId, + string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null, + DomainHost host = null) + { + return await SendOrderCustomerMessage(MessageTemplateNames.SendOrderPlacedCustomerMessage, order, customer, + languageId, attachmentFilePath, attachmentFileName, attachments, host: host); + } + + /// + /// Sends an order paid notification to a customer + /// + /// Order instance + /// Customer instance + /// Message language identifier + /// Attachment file path + /// + /// Attachment file name. If specified, then this file name will be sent to a recipient. + /// Otherwise, "AttachmentFilePath" name will be used. + /// + /// Attachments ident + public virtual async Task SendOrderPaidCustomerMessage(Order order, Customer customer, string languageId, + string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null) + { + return await SendOrderCustomerMessage(MessageTemplateNames.SendOrderPaidCustomerMessage, order, customer, + languageId, attachmentFilePath, attachmentFileName, attachments); + } + + /// + /// Sends an order completed notification to a customer + /// + /// Order instance + /// Customer instance + /// Message language identifier + /// Attachment file path + /// + /// Attachment file name. If specified, then this file name will be sent to a recipient. + /// Otherwise, "AttachmentFilePath" name will be used. + /// + /// Attachments ident + public virtual async Task SendOrderCompletedCustomerMessage(Order order, Customer customer, string languageId, + string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null) + { + return await SendOrderCustomerMessage(MessageTemplateNames.SendOrderCompletedCustomerMessage, order, customer, + languageId, attachmentFilePath, attachmentFileName, attachments); + } + + /// + /// Sends an order cancelled notification to a customer + /// + /// Order instance + /// Customer instance + /// Message language identifier + public virtual async Task SendOrderCancelledCustomerMessage(Order order, Customer customer, string languageId) + { + return await SendOrderCustomerMessage(MessageTemplateNames.SendOrderCancelledCustomerMessage, order, customer, + languageId); + } + + /// + /// Sends an order refunded notification to a customer + /// + /// Order instance + /// Amount refunded + /// Message language identifier + /// Queued email identifier + public virtual async Task SendOrderRefundedCustomerMessage(Order order, double refundedAmount, + string languageId) + { + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = order.CustomerId }); + return await SendOrderCustomerMessage(MessageTemplateNames.SendOrderRefundedCustomerMessage, order, customer, + languageId, refundedAmount: refundedAmount); + } + + /// + /// Sends an order notification to a customer + /// + /// Message template + /// Order instance + /// Customer instance + /// Message language identifier + /// Attachment file path + /// + /// Attachment file name. If specified, then this file name will be sent to a recipient. + /// Otherwise, "AttachmentFilePath" name will be used. + /// + /// Attachments ident + /// + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + private async Task SendOrderCustomerMessage(string message, Order order, Customer customer, string languageId, + string attachmentFilePath = null, string attachmentFileName = null, IEnumerable attachments = null, + double refundedAmount = 0, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(order); + + var store = await _messageLookupService.GetStore(order.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate(message, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddOrderTokens(order, customer, store, host); + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = order.BillingAddress.Email; + var toName = $"{order.BillingAddress.FirstName} {order.BillingAddress.LastName}"; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + attachmentFilePath, + attachmentFileName, + attachments, + reference: Reference.Order, objectId: order.Id); + } + + /// + /// Sends an order placed notification to a vendor + /// + /// Order instance + /// Customer instance + /// Vendor instance + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendOrderPlacedVendorMessage(Order order, Customer customer, Vendor vendor, + string languageId, DomainHost host = null) + { + return await SendOrderVendorMessage(MessageTemplateNames.SendOrderPlacedVendorMessage, order, vendor, + languageId, host: host); + } + + /// + /// Sends an order paid notification to a vendor + /// + /// Order instance + /// Vendor instance + /// Message language identifier + public virtual async Task SendOrderPaidVendorMessage(Order order, Vendor vendor, string languageId) + { + return await SendOrderVendorMessage(MessageTemplateNames.SendOrderPaidVendorMessage, order, vendor, languageId); + } + + /// + /// Sends an order cancel notification to a vendor + /// + /// Order instance + /// Vendor instance + /// Message language identifier + public virtual async Task SendOrderCancelledVendorMessage(Order order, Vendor vendor, string languageId) + { + return await SendOrderVendorMessage(MessageTemplateNames.SendOrderCancelledVendorMessage, order, vendor, + languageId); + } + + /// + /// Sends an order notification to a vendor + /// + /// Message template + /// Order instance + /// Vendor instance + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + private async Task SendOrderVendorMessage(string message, Order order, Vendor vendor, string languageId, + DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(order); + ArgumentNullException.ThrowIfNull(vendor); + + var store = await _messageLookupService.GetStore(order.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate(message, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = order.CustomerId }); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddOrderTokens(order, customer, store, host, vendor: vendor); + + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = vendor.Email; + var toName = vendor.Name; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Order, objectId: order.Id); + } + + /// + /// Sends a shipment sent notification to a customer + /// + /// Shipment + /// Order + public virtual async Task SendShipmentSentCustomerMessage(Shipment shipment, Order order) + { + return await SendShipmentCustomerMessage(MessageTemplateNames.SendShipmentSentCustomerMessage, shipment, order); + } + + /// + /// Sends a shipment delivered notification to a customer + /// + /// Shipment + /// Order + public virtual async Task SendShipmentDeliveredCustomerMessage(Shipment shipment, Order order) + { + return await SendShipmentCustomerMessage(MessageTemplateNames.SendShipmentDeliveredCustomerMessage, shipment, + order); + } + + /// + /// Send a shipment notification to a customer + /// + /// Message template + /// Shipment + /// Order + private async Task SendShipmentCustomerMessage(string message, Shipment shipment, Order order) + { + ArgumentNullException.ThrowIfNull(shipment); + + if (order == null) + throw new Exception("Order cannot be loaded"); + + var store = await _messageLookupService.GetStore(order.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(order.CustomerLanguageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate(message, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = order.CustomerId }); + + var host = store.PrimaryHost(); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddShipmentTokens(shipment, order, store, host, language) + .AddOrderTokens(order, customer, store, host); + + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = order.BillingAddress.Email; + var toName = $"{order.BillingAddress.FirstName} {order.BillingAddress.LastName}"; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + language.Id, liquidObject, + toEmail, toName, + reference: Reference.Shipment, objectId: shipment.Id); + } + + /// + /// Sends a new order note added notification to a customer + /// + /// Order + /// Order note + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendNewOrderNoteAddedCustomerMessage(Order order, OrderNote orderNote, + DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(orderNote); + ArgumentNullException.ThrowIfNull(order); + + var store = await _messageLookupService.GetStore(order.StoreId); + var language = await _messageLookupService.EnsureLanguageIsActive(order.CustomerLanguageId, store.Id); + + var messageTemplate = + await _messageLookupService.GetMessageTemplate(MessageTemplateNames.SendNewOrderNoteAddedCustomerMessage, store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = order.CustomerId }); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddOrderTokens(order, customer, store, host, orderNote); + + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = order.BillingAddress.Email; + var toName = $"{order.BillingAddress.FirstName} {order.BillingAddress.LastName}"; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + language.Id, liquidObject, + toEmail, toName, + reference: Reference.Order, objectId: order.Id); + } + + /// + /// Sends a gift voucher notification + /// + /// Gift voucher + /// Order + /// Message language identifier + public virtual async Task SendGiftVoucherMessage(GiftVoucher giftVoucher, Order order, string languageId) + { + ArgumentNullException.ThrowIfNull(giftVoucher); + + var store = await _messageLookupService.GetStore(order?.StoreId); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store?.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("GiftVoucher.Notification", store?.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddGiftVoucherTokens(giftVoucher, language); + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + var toEmail = giftVoucher.RecipientEmail; + var toName = giftVoucher.RecipientName; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName); + } + + #endregion +} diff --git a/src/Business/Grand.Business.Messages/Services/VendorMessageProviderService.cs b/src/Business/Grand.Business.Messages/Services/VendorMessageProviderService.cs new file mode 100644 index 0000000000..2baaf0756f --- /dev/null +++ b/src/Business/Grand.Business.Messages/Services/VendorMessageProviderService.cs @@ -0,0 +1,254 @@ +using Grand.Business.Core.Commands.Messages.Common; +using Grand.Business.Core.Extensions; +using Grand.Business.Core.Interfaces.Messages; +using Grand.Business.Core.Queries.Messages; +using Grand.Business.Messages.DotLiquidDrops; +using Grand.Domain.Common; +using Grand.Domain.Customers; +using Grand.Domain.Localization; +using Grand.Domain.Messages; +using Grand.Domain.Stores; +using Grand.Domain.Vendors; +using Grand.Mediator; +using Grand.SharedKernel.Extensions; +using System.Net; + +namespace Grand.Business.Messages.Services; + +public class VendorMessageProviderService : IVendorMessageProviderService +{ + #region Ctor + + public VendorMessageProviderService( + IMessageLookupService messageLookupService, + IMessageProviderService messageProviderService, + IMediator mediator, + CommonSettings commonSettings) + { + _messageLookupService = messageLookupService; + _messageProviderService = messageProviderService; + _mediator = mediator; + _commonSettings = commonSettings; + } + + #endregion + + #region Fields + + private readonly IMessageLookupService _messageLookupService; + private readonly IMessageProviderService _messageProviderService; + private readonly IMediator _mediator; + private readonly CommonSettings _commonSettings; + + #endregion + + #region Methods + + /// + /// Sends 'New vendor account submitted' message to a store owner + /// + /// Customer + /// Vendor + /// Store + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendNewVendorAccountApplyStoreOwnerMessage(Customer customer, Vendor vendor, + Store store, string languageId, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(customer); + ArgumentNullException.ThrowIfNull(vendor); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("VendorAccountApply.StoreOwnerNotification", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator).AddStoreTokens(store, language, emailAccount) + .AddCustomerTokens(customer, store, host, language) + .AddVendorTokens(vendor, language); + + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Customer, objectId: customer.Id); + } + + /// + /// Sends 'Vendor information changed' message to a store owner + /// + /// Vendor + /// Store + /// Message language identifier + public virtual async Task SendVendorInformationChangeMessage(Vendor vendor, Store store, string languageId) + { + ArgumentNullException.ThrowIfNull(vendor); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("VendorInformationChange.StoreOwnerNotification", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddVendorTokens(vendor, language); + var liquidObject = await builder.BuildAsync(); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = emailAccount.Email; + var toName = emailAccount.DisplayName; + + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, languageId, liquidObject, toEmail, toName, + reference: Reference.Vendor, objectId: vendor.Id); + } + + /// + /// Sends a vendor review notification message to a store owner + /// + /// Vendor review + /// Store + /// Message language identifier + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendVendorReviewMessage(VendorReview vendorReview, Store store, + string languageId, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(vendorReview); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("Vendor.VendorReview", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + //customer + var customer = await _mediator.Send(new GetCustomerByIdQuery { Id = vendorReview.CustomerId }); + //vendor + var vendor = await _mediator.Send(new GetVendorByIdQuery { Id = vendorReview.VendorId }); + if (vendor == null) + return 0; + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddVendorReviewTokens(vendor, vendorReview); + builder.AddCustomerTokensIfNotNull(customer, store, host, language); + + builder.AddVendorTokens(vendor, language); + var liquidObject = await builder.BuildAsync(); + + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = vendor.Email; + var toName = vendor.Name; + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, + languageId, liquidObject, + toEmail, toName, + reference: Reference.Customer, objectId: customer?.Id); + } + + /// + /// Sends "contact vendor" message + /// + /// Customer + /// Store + /// Vendor + /// Message language identifier + /// Sender email + /// Sender name + /// Email subject. Pass null if you want a message template subject to be used. + /// Email body + /// Ip address + /// + /// Host of the storefront request; links use it when it is one of the store's domains, else the primary domain + /// + public virtual async Task SendContactVendorMessage(Customer customer, Store store, Vendor vendor, + string languageId, string senderEmail, + string senderName, string subject, string body, string ipaddress, DomainHost host = null) + { + ArgumentNullException.ThrowIfNull(vendor); + + var language = await _messageLookupService.EnsureLanguageIsActive(languageId, store.Id); + + var messageTemplate = await _messageLookupService.GetMessageTemplate("Service.ContactVendor", store.Id); + if (messageTemplate == null) + return 0; + + //email account + var emailAccount = await _messageLookupService.GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); + + string fromEmail; + string fromName; + senderName = WebUtility.HtmlEncode(senderName); + senderEmail = WebUtility.HtmlEncode(senderEmail); + + //required for some SMTP servers + if (_commonSettings.UseSystemEmailForContactUsForm) + { + fromEmail = emailAccount.Email; + fromName = emailAccount.DisplayName; + } + else + { + fromEmail = senderEmail; + fromName = senderName; + } + + host = store.LinkHost(host); + var builder = new LiquidObjectBuilder(_mediator); + builder.AddStoreTokens(store, language, emailAccount) + .AddCustomerTokens(customer, store, host, language); + var liquidObject = await builder.BuildAsync(); + liquidObject.ContactUs = new LiquidContactUs(senderEmail, senderName, body, ""); + //event notification + await _mediator.MessageTokensAdded(messageTemplate, liquidObject); + + var toEmail = vendor.Email; + var toName = vendor.Name; + + //store in database + if (_commonSettings.StoreInDatabaseContactUsForm) + await _mediator.Send(new InsertContactUsCommand { + CustomerId = customer.Id, + StoreId = store.Id, + VendorId = vendor.Id, + Email = senderEmail, + Enquiry = body, + RemoteIpAddress = ipaddress, + FullName = senderName, + Subject = string.IsNullOrEmpty(subject) ? "Contact Us (form)" : subject, + EmailAccountId = emailAccount.Id + }); + + return await _messageProviderService.SendNotification(messageTemplate, emailAccount, languageId, liquidObject, toEmail, toName, + fromEmail: fromEmail, + fromName: fromName, + subject: subject, + replyToEmailAddress: senderEmail, + replyToName: senderName, + reference: Reference.Vendor, objectId: vendor.Id); + } + + #endregion +} diff --git a/src/Business/Grand.Business.Messages/Startup/StartupApplication.cs b/src/Business/Grand.Business.Messages/Startup/StartupApplication.cs index 08e82f523a..bb9b1f3de0 100644 --- a/src/Business/Grand.Business.Messages/Startup/StartupApplication.cs +++ b/src/Business/Grand.Business.Messages/Startup/StartupApplication.cs @@ -17,6 +17,14 @@ public void ConfigureServices(IServiceCollection services, IConfiguration config services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Modules/Grand.Module.ScheduledTasks/BackgroundServices/EndAuctionsTask.cs b/src/Modules/Grand.Module.ScheduledTasks/BackgroundServices/EndAuctionsTask.cs index da64e606ed..055f5f7273 100644 --- a/src/Modules/Grand.Module.ScheduledTasks/BackgroundServices/EndAuctionsTask.cs +++ b/src/Modules/Grand.Module.ScheduledTasks/BackgroundServices/EndAuctionsTask.cs @@ -22,17 +22,17 @@ public class EndAuctionsTask : IScheduleTask private readonly ILanguageService _languageService; private readonly LanguageSettings _languageSettings; private readonly ILogger _logger; - private readonly IMessageProviderService _messageProviderService; + private readonly ICatalogMessageProviderService _catalogMessageProviderService; private readonly IShoppingCartService _shoppingCartService; public EndAuctionsTask(IAuctionService auctionService, - IMessageProviderService messageProviderService, LanguageSettings translationService, + ICatalogMessageProviderService catalogMessageProviderService, LanguageSettings translationService, IShoppingCartService shoppingCartService, ICustomerService customerService, ILanguageService languageService, ILogger logger) { _languageService = languageService; _auctionService = auctionService; - _messageProviderService = messageProviderService; + _catalogMessageProviderService = catalogMessageProviderService; _languageSettings = translationService; _shoppingCartService = shoppingCartService; _customerService = customerService; @@ -59,7 +59,7 @@ public async Task Execute(CancellationToken cancellationToken) if (bid == null) { await _auctionService.UpdateAuctionEnded(auctionToEnd, true); - await _messageProviderService.SendAuctionEndedStoreOwnerMessage(auctionToEnd, + await _catalogMessageProviderService.SendAuctionEndedStoreOwnerMessage(auctionToEnd, _languageSettings.DefaultAdminLanguageId, null); continue; } @@ -81,10 +81,10 @@ await _messageProviderService.SendAuctionEndedStoreOwnerMessage(auctionToEnd, { bid.Win = true; await _auctionService.UpdateBid(bid); - await _messageProviderService.SendAuctionEndedStoreOwnerMessage(auctionToEnd, + await _catalogMessageProviderService.SendAuctionEndedStoreOwnerMessage(auctionToEnd, _languageSettings.DefaultAdminLanguageId, bid); - await _messageProviderService.SendAuctionWinEndedCustomerMessage(auctionToEnd, null, bid); - await _messageProviderService.SendAuctionEndedLostCustomerMessage(auctionToEnd, null, bid); + await _catalogMessageProviderService.SendAuctionWinEndedCustomerMessage(auctionToEnd, null, bid); + await _catalogMessageProviderService.SendAuctionEndedLostCustomerMessage(auctionToEnd, null, bid); await _auctionService.UpdateAuctionEnded(auctionToEnd, true); } else diff --git a/src/Plugins/Authentication.Facebook/Infrastructure/Cache/FacebookAuthenticationEventConsumer.cs b/src/Plugins/Authentication.Facebook/Infrastructure/Cache/FacebookAuthenticationEventConsumer.cs index 2d5c75209c..ceea1df9e9 100644 --- a/src/Plugins/Authentication.Facebook/Infrastructure/Cache/FacebookAuthenticationEventConsumer.cs +++ b/src/Plugins/Authentication.Facebook/Infrastructure/Cache/FacebookAuthenticationEventConsumer.cs @@ -17,7 +17,7 @@ public class FacebookAuthenticationEventConsumer : INotificationHandler _contextAccessor.WorkContext.WorkingLanguage.Id; @@ -29,13 +29,13 @@ public class GoogleAuthenticationEventConsumer : INotificationHandler _giftVoucherMock; private ActivatedValueForPurchasedGiftVouchersCommandHandler _handler; private Mock _langService; - private Mock _messageProviderMock; + private Mock _messageProviderMock; [TestInitialize] public void Init() { _giftVoucherMock = new Mock(); _langService = new Mock(); - _messageProviderMock = new Mock(); + _messageProviderMock = new Mock(); _handler = new ActivatedValueForPurchasedGiftVouchersCommandHandler(_giftVoucherMock.Object, _langService.Object, _messageProviderMock.Object); } diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/OrderNotificationCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/OrderNotificationCommandHandlerTests.cs index fc0213c050..7c869832c1 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/OrderNotificationCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/OrderNotificationCommandHandlerTests.cs @@ -17,15 +17,15 @@ namespace Grand.Business.Checkout.Tests.Commands.Handlers.Orders; public class OrderNotificationCommandHandlerTests { private OrderNotificationCommandHandler _handler; - private Mock _messageProviderServiceMock; + private Mock _orderMessageProviderServiceMock; private Mock _orderServiceMock; [TestInitialize] public void Init() { _orderServiceMock = new Mock(); - _messageProviderServiceMock = new Mock(); - _handler = new OrderNotificationCommandHandler(_orderServiceMock.Object, _messageProviderServiceMock.Object, + _orderMessageProviderServiceMock = new Mock(); + _handler = new OrderNotificationCommandHandler(_orderServiceMock.Object, _orderMessageProviderServiceMock.Object, new OrderSettings(), new LanguageSettings(), new Mock().Object, new Mock>().Object, new Mock().Object); } @@ -57,7 +57,7 @@ public async Task Handle_SendsTheCustomerMessageForTheCommandCustomer() await _handler.Handle(new OrderNotificationCommand { Order = order, Customer = customer }, CancellationToken.None); //Assert - _messageProviderServiceMock.Verify(x => x.SendOrderPlacedCustomerMessage(order, customer, "en", + _orderMessageProviderServiceMock.Verify(x => x.SendOrderPlacedCustomerMessage(order, customer, "en", It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); } } diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/OrderStatusCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/OrderStatusCommandHandlerTests.cs index 41ad3261ce..a633ae22ab 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/OrderStatusCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/OrderStatusCommandHandlerTests.cs @@ -18,7 +18,7 @@ public class OrderStatusCommandHandlerTests private OrderStatusCommandHandler _handler; private LoyaltyPointsSettings _loyaltyPointsSettings; private Mock _mediatorMock; - private Mock _messageProviderServiceMock; + private Mock _orderMessageProviderServiceMock; private Mock _orderServiceMock; private OrderSettings _orderSettings; private Mock _pdfServiceMock; @@ -31,13 +31,13 @@ public void Init() _orderServiceMock = new Mock(); _customerServiceMock = new Mock(); _pdfServiceMock = new Mock(); - _messageProviderServiceMock = new Mock(); + _orderMessageProviderServiceMock = new Mock(); _vendorServiceMock = new Mock(); _loyaltyPointsSettings = new LoyaltyPointsSettings(); _orderSettings = new OrderSettings(); _handler = new OrderStatusCommandHandler(_orderServiceMock.Object, _customerServiceMock.Object, - _pdfServiceMock.Object, _messageProviderServiceMock.Object, _vendorServiceMock.Object, _mediatorMock.Object, + _pdfServiceMock.Object, _orderMessageProviderServiceMock.Object, _vendorServiceMock.Object, _mediatorMock.Object, _orderSettings, _loyaltyPointsSettings); } diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PartiallyRefundCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PartiallyRefundCommandHandlerTests.cs index 230fae2a74..d1eb575abb 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PartiallyRefundCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PartiallyRefundCommandHandlerTests.cs @@ -21,7 +21,7 @@ public class PartiallyRefundCommandHandlerTests private PartiallyRefundCommandHandler _handler; private Mock> _loggerMock; private Mock _mediatorMock; - private Mock _messageProviderServiceMock; + private Mock _orderMessageProviderServiceMock; private Mock _orderServiceMock; private Mock _paymentServiceMock; private Mock _paymentTransactionMock; @@ -32,12 +32,12 @@ public void Init() _mediatorMock = new Mock(); _orderServiceMock = new Mock(); _paymentTransactionMock = new Mock(); - _messageProviderServiceMock = new Mock(); + _orderMessageProviderServiceMock = new Mock(); _paymentServiceMock = new Mock(); _loggerMock = new Mock>(); _handler = new PartiallyRefundCommandHandler(_paymentServiceMock.Object, _paymentTransactionMock.Object, - _mediatorMock.Object, _messageProviderServiceMock.Object, _loggerMock.Object, _orderServiceMock.Object, + _mediatorMock.Object, _orderMessageProviderServiceMock.Object, _loggerMock.Object, _orderServiceMock.Object, new LanguageSettings()); } diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PartiallyRefundOfflineCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PartiallyRefundOfflineCommandHandlerTests.cs index 85b4b24923..a95e00caca 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PartiallyRefundOfflineCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PartiallyRefundOfflineCommandHandlerTests.cs @@ -18,7 +18,7 @@ public class PartiallyRefundOfflineCommandHandlerTests { private PartiallyRefundOfflineCommandHandler _handler; private Mock _mediatorMock; - private Mock _messageProviderServiceMock; + private Mock _orderMessageProviderServiceMock; private Mock _orderServiceMock; private Mock _paymentTransactionMock; @@ -28,10 +28,10 @@ public void Init() _mediatorMock = new Mock(); _orderServiceMock = new Mock(); _paymentTransactionMock = new Mock(); - _messageProviderServiceMock = new Mock(); + _orderMessageProviderServiceMock = new Mock(); _handler = new PartiallyRefundOfflineCommandHandler(_orderServiceMock.Object, _paymentTransactionMock.Object, - _mediatorMock.Object, _messageProviderServiceMock.Object, new LanguageSettings()); + _mediatorMock.Object, _orderMessageProviderServiceMock.Object, new LanguageSettings()); } [TestMethod] diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PlaceOrderCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PlaceOrderCommandHandlerTests.cs index 3a9600a974..7ded646e45 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PlaceOrderCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/PlaceOrderCommandHandlerTests.cs @@ -50,7 +50,7 @@ public void Init() new Mock().Object, new Mock().Object, new Mock().Object, _checkoutAttributeFormatterMock.Object, new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, + new Mock().Object, new Mock().Object, new Mock().Object, new Mock().Object, currencyService.Object, new Mock().Object, new Mock().Object, new Mock().Object, new Mock().Object, diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/RefundCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/RefundCommandHandlerTests.cs index e5e8cf9c64..abee1b20e6 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/RefundCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/RefundCommandHandlerTests.cs @@ -21,7 +21,7 @@ public class RefundCommandHandlerTests private RefundCommandHandler _handler; private Mock> _loggerMock; private Mock _mediatorMock; - private Mock _messageProviderServiceMock; + private Mock _orderMessageProviderServiceMock; private Mock _orderServiceMock; private Mock _paymentServiceMock; private Mock _paymentTransactionMock; @@ -32,12 +32,12 @@ public void Init() _mediatorMock = new Mock(); _orderServiceMock = new Mock(); _paymentTransactionMock = new Mock(); - _messageProviderServiceMock = new Mock(); + _orderMessageProviderServiceMock = new Mock(); _paymentServiceMock = new Mock(); _loggerMock = new Mock>(); _handler = new RefundCommandHandler(_mediatorMock.Object, _paymentServiceMock.Object, - _paymentTransactionMock.Object, _orderServiceMock.Object, _messageProviderServiceMock.Object, + _paymentTransactionMock.Object, _orderServiceMock.Object, _orderMessageProviderServiceMock.Object, _loggerMock.Object, new LanguageSettings()); } diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/VoidCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/VoidCommandHandlerTests.cs index 15e6ba97a7..1cba39df3f 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/VoidCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/VoidCommandHandlerTests.cs @@ -20,7 +20,7 @@ public class VoidCommandHandlerTests private VoidCommandHandler _handler; private Mock> _loggerMock; private Mock _mediatorMock; - private Mock _messageProviderServiceMock; + private Mock _orderMessageProviderServiceMock; private Mock _orderServiceMock; private Mock _paymentServiceMock; private Mock _paymentTransactionMock; @@ -31,7 +31,7 @@ public void Init() _mediatorMock = new Mock(); _orderServiceMock = new Mock(); _paymentTransactionMock = new Mock(); - _messageProviderServiceMock = new Mock(); + _orderMessageProviderServiceMock = new Mock(); _paymentServiceMock = new Mock(); _loggerMock = new Mock>(); diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Shipping/DeliveryCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Shipping/DeliveryCommandHandlerTests.cs index b146cc3e7e..cf5ee4fb3d 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Shipping/DeliveryCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Shipping/DeliveryCommandHandlerTests.cs @@ -16,7 +16,7 @@ public class DeliveryCommandHandlerTests { private DeliveryCommandHandler _deliveryCommandHandler; private Mock _mediatorMock; - private Mock _messageProviderServiceMock; + private Mock _orderMessageProviderServiceMock; private Mock _orderServiceMock; private Mock _shipmentServiceMock; @@ -26,10 +26,10 @@ public void Init() _mediatorMock = new Mock(); _orderServiceMock = new Mock(); _shipmentServiceMock = new Mock(); - _messageProviderServiceMock = new Mock(); + _orderMessageProviderServiceMock = new Mock(); _deliveryCommandHandler = new DeliveryCommandHandler(_mediatorMock.Object, _orderServiceMock.Object, - _shipmentServiceMock.Object, _messageProviderServiceMock.Object); + _shipmentServiceMock.Object, _orderMessageProviderServiceMock.Object); } [TestMethod] diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Shipping/ShipCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Shipping/ShipCommandHandlerTests.cs index bd91dd75aa..40b9fc0b64 100644 --- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Shipping/ShipCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Shipping/ShipCommandHandlerTests.cs @@ -15,7 +15,7 @@ namespace Grand.Business.Checkout.Tests.Commands.Handlers.Shipping; public class ShipCommandHandlerTests { private Mock _mediatorMock; - private Mock _messageProviderServiceMock; + private Mock _orderMessageProviderServiceMock; private Mock _orderServiceMock; private ShipCommandHandler _shipCommandHandler; private Mock _shipmentServiceMock; @@ -26,10 +26,10 @@ public void Init() _mediatorMock = new Mock(); _orderServiceMock = new Mock(); _shipmentServiceMock = new Mock(); - _messageProviderServiceMock = new Mock(); + _orderMessageProviderServiceMock = new Mock(); _shipCommandHandler = new ShipCommandHandler(_mediatorMock.Object, _orderServiceMock.Object, - _shipmentServiceMock.Object, _messageProviderServiceMock.Object); + _shipmentServiceMock.Object, _orderMessageProviderServiceMock.Object); } [TestMethod] diff --git a/src/Tests/Grand.Business.Messages.Tests/Services/MessageProviderServiceTest.cs b/src/Tests/Grand.Business.Messages.Tests/Services/MessageProviderServiceTest.cs index 9b826f3720..58e60f68f8 100644 --- a/src/Tests/Grand.Business.Messages.Tests/Services/MessageProviderServiceTest.cs +++ b/src/Tests/Grand.Business.Messages.Tests/Services/MessageProviderServiceTest.cs @@ -7,11 +7,13 @@ using Grand.Business.Messages.DotLiquidDrops; using Grand.Business.Messages.Services; using Grand.Domain; +using Grand.Domain.Blogs; using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Customers; using Grand.Domain.Localization; using Grand.Domain.Messages; +using Grand.Domain.Orders; using Grand.Domain.Stores; using Grand.Domain.Vendors; using Grand.Mediator; @@ -30,6 +32,13 @@ public class MessageProviderServiceTest private Mock _languageServiceMock; private Mock _mediatorMock; private MessageProviderService _messageService; + private CustomerMessageProviderService _customerMessageService; + private CatalogMessageProviderService _catalogMessageService; + private VendorMessageProviderService _vendorMessageService; + private OrderMessageProviderService _orderMessageService; + private NewsletterMessageProviderService _newsletterMessageService; + private MerchandiseReturnMessageProviderService _merchandiseReturnMessageService; + private CmsMessageProviderService _cmsMessageService; private Mock _messageTemplateServiceMock; private Mock _messageTokenProviderMock; private Product _product; @@ -70,37 +79,48 @@ public void Init() _emailAccountSettingsMock = new Mock(); _commonSetting = new Mock(); - _messageService = new MessageProviderService( + _messageService = new MessageProviderService(_queuedEmailServiceMock.Object); + var messageLookupService = new MessageLookupService( _messageTemplateServiceMock.Object, - _queuedEmailServiceMock.Object, _languageServiceMock.Object, _emailAccountServiceMock.Object, _storeServiceMock.Object, - _groupService.Object, - _mediatorMock.Object, - _emailAccountSettingsMock.Object, - _commonSetting.Object); + _emailAccountSettingsMock.Object); + _customerMessageService = new CustomerMessageProviderService(messageLookupService, _messageService, + _mediatorMock.Object, _commonSetting.Object); + _catalogMessageService = new CatalogMessageProviderService(messageLookupService, _messageService, + _mediatorMock.Object, _commonSetting.Object); + _vendorMessageService = new VendorMessageProviderService(messageLookupService, _messageService, + _mediatorMock.Object, _commonSetting.Object); + _orderMessageService = new OrderMessageProviderService(messageLookupService, _messageService, + _mediatorMock.Object); + _newsletterMessageService = new NewsletterMessageProviderService(messageLookupService, _messageService, + _mediatorMock.Object); + _merchandiseReturnMessageService = new MerchandiseReturnMessageProviderService(messageLookupService, + _messageService, _mediatorMock.Object); + _cmsMessageService = new CmsMessageProviderService(messageLookupService, _messageService, + _mediatorMock.Object, _groupService.Object); _product = new Product(); } [TestMethod] public async Task CallInsertQueuedEmailMethod() { - var result = await _messageService.SendOutBidCustomerMessage(new Product(), "123", new Bid()); + var result = await _catalogMessageService.SendOutBidCustomerMessage(new Product(), "123", new Bid()); _queuedEmailServiceMock.Verify(x => x.InsertQueuedEmail(It.IsAny()), Times.Once()); } [TestMethod] public async Task SendOutBidCustomerNotificationMethodReturnCorrectResult() { - var result = await _messageService.SendOutBidCustomerMessage(new Product(), "123", new Bid()); + var result = await _catalogMessageService.SendOutBidCustomerMessage(new Product(), "123", new Bid()); Assert.AreEqual(1, result); } [TestMethod] public async Task SendNewVendorAccountApplyStoreOwnerNotificationRetunsCorrectResult() { - var result = await _messageService.SendNewVendorAccountApplyStoreOwnerMessage(new Customer(), new Vendor(), + var result = await _vendorMessageService.SendNewVendorAccountApplyStoreOwnerMessage(new Customer(), new Vendor(), new Store { Url = "https://localhost:44350/" }, "123"); Assert.AreEqual(1, result); } @@ -118,6 +138,44 @@ public async Task SendNotificationMethodToEmailIsNull() Assert.AreEqual(0, result); } + [TestMethod] + public async Task SendOrderPaidStoreOwnerMessage_QueuesEmail() + { + var result = await _orderMessageService.SendOrderPaidStoreOwnerMessage(new Order(), new Customer(), "123"); + + Assert.AreEqual(1, result); + _queuedEmailServiceMock.Verify(x => x.InsertQueuedEmail(It.IsAny()), Times.Once()); + } + + [TestMethod] + public async Task SendNewsLetterSubscriptionActivationMessage_QueuesEmail() + { + var result = await _newsletterMessageService.SendNewsLetterSubscriptionActivationMessage( + new NewsLetterSubscription { Email = "a@b.c" }, "123"); + + Assert.AreEqual(1, result); + _queuedEmailServiceMock.Verify(x => x.InsertQueuedEmail(It.IsAny()), Times.Once()); + } + + [TestMethod] + public async Task SendNewMerchandiseReturnStoreOwnerMessage_QueuesEmail() + { + var result = await _merchandiseReturnMessageService.SendNewMerchandiseReturnStoreOwnerMessage( + new MerchandiseReturn(), new Order(), "123"); + + Assert.AreEqual(1, result); + _queuedEmailServiceMock.Verify(x => x.InsertQueuedEmail(It.IsAny()), Times.Once()); + } + + [TestMethod] + public async Task SendBlogCommentMessage_QueuesEmail() + { + var result = await _cmsMessageService.SendBlogCommentMessage(new BlogPost(), new BlogComment(), "123"); + + Assert.AreEqual(1, result); + _queuedEmailServiceMock.Verify(x => x.InsertQueuedEmail(It.IsAny()), Times.Once()); + } + [TestMethod] public async Task SendCustomerMessage_LinksPointToThePrimaryDomainOfTheStore() { @@ -149,7 +207,7 @@ private async Task PasswordRecoveryUrl(Store store) .Callback, CancellationToken>((e, _) => liquidCustomer = (LiquidCustomer)e.LiquidDrop); - await _messageService.SendCustomerPasswordRecoveryMessage(new Customer { Email = "a@b.c" }, store, ""); + await _customerMessageService.SendCustomerPasswordRecoveryMessage(new Customer { Email = "a@b.c" }, store, ""); Assert.IsNotNull(liquidCustomer); return liquidCustomer.PasswordRecoveryURL; diff --git a/src/Tests/Grand.Modules.Tests/Commands/SendOutBidCustomerNotificationCommandHandlerTests.cs b/src/Tests/Grand.Modules.Tests/Commands/SendOutBidCustomerNotificationCommandHandlerTests.cs index cf8d07f5b8..725b7b8bf0 100644 --- a/src/Tests/Grand.Modules.Tests/Commands/SendOutBidCustomerNotificationCommandHandlerTests.cs +++ b/src/Tests/Grand.Modules.Tests/Commands/SendOutBidCustomerNotificationCommandHandlerTests.cs @@ -11,12 +11,12 @@ namespace Grand.Modules.Tests.Commands; public class SendOutBidCustomerNotificationCommandHandlerTests { private SendOutBidCustomerNotificationCommandHandler _handler; - private Mock _messageProviderMock; + private Mock _messageProviderMock; [TestInitialize] public void Init() { - _messageProviderMock = new Mock(); + _messageProviderMock = new Mock(); _handler = new SendOutBidCustomerNotificationCommandHandler(_messageProviderMock.Object); } diff --git a/src/Tests/Grand.Modules.Tests/Commands/SendQuantityBelowStoreOwnerNotificationCommandHandlerTests.cs b/src/Tests/Grand.Modules.Tests/Commands/SendQuantityBelowStoreOwnerNotificationCommandHandlerTests.cs index d611621672..b80368cca5 100644 --- a/src/Tests/Grand.Modules.Tests/Commands/SendQuantityBelowStoreOwnerNotificationCommandHandlerTests.cs +++ b/src/Tests/Grand.Modules.Tests/Commands/SendQuantityBelowStoreOwnerNotificationCommandHandlerTests.cs @@ -12,13 +12,13 @@ namespace Grand.Modules.Tests.Commands; public class SendQuantityBelowStoreOwnerNotificationCommandHandlerTests { private SendQuantityBelowStoreOwnerNotificationCommandHandler _handler; - private Mock _messageProviderMock; + private Mock _messageProviderMock; private LanguageSettings _settings; [TestInitialize] public void Init() { - _messageProviderMock = new Mock(); + _messageProviderMock = new Mock(); _settings = new LanguageSettings(); _handler = new SendQuantityBelowStoreOwnerNotificationCommandHandler(_messageProviderMock.Object, _settings); } diff --git a/src/Tests/Grand.Modules.Tests/Services/BackgroundService/EndAuctionsTaskTests.cs b/src/Tests/Grand.Modules.Tests/Services/BackgroundService/EndAuctionsTaskTests.cs index 76fb460d6f..2e41403097 100644 --- a/src/Tests/Grand.Modules.Tests/Services/BackgroundService/EndAuctionsTaskTests.cs +++ b/src/Tests/Grand.Modules.Tests/Services/BackgroundService/EndAuctionsTaskTests.cs @@ -24,7 +24,7 @@ public class EndAuctionsTaskTests private Mock _customerServiceMock; private Mock _languageServiceMock; private Mock> _loggerMock; - private Mock _messageProviderMock; + private Mock _messageProviderMock; private LanguageSettings _settings; private Mock _shoppingCartMock; private EndAuctionsTask _task; @@ -33,7 +33,7 @@ public class EndAuctionsTaskTests public void Init() { _auctionMock = new Mock(); - _messageProviderMock = new Mock(); + _messageProviderMock = new Mock(); _shoppingCartMock = new Mock(); _customerServiceMock = new Mock(); _loggerMock = new Mock>(); diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCustomerControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCustomerControllerTests.cs index ae96d0a240..8ba8e8beb3 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCustomerControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCustomerControllerTests.cs @@ -20,14 +20,14 @@ public class TestCustomerController( Grand.Business.Core.Interfaces.Customers.ICustomerManagerService customerManagerService, Grand.Business.Core.Interfaces.Customers.ICustomerAttributeParser customerAttributeParser, Grand.Business.Core.Interfaces.Customers.ICustomerAttributeService customerAttributeService, - Grand.Business.Core.Interfaces.Messages.IMessageProviderService messageProviderService, + Grand.Business.Core.Interfaces.Messages.ICustomerMessageProviderService customerMessageProviderService, Grand.Business.Core.Interfaces.Common.Directory.IGroupService groupService, Grand.Web.Common.Localization.IRequestTranslationService translationService, IContextAccessor contextAccessor, CustomerSettings customerSettings, Grand.Web.AdminShared.Interfaces.IAdminDataScope scope) : BaseCustomerController(customerService, customerViewModelService, customerManagerService, - customerAttributeParser, customerAttributeService, messageProviderService, groupService, + customerAttributeParser, customerAttributeService, customerMessageProviderService, groupService, translationService, contextAccessor, customerSettings, scope) { public Task<(Customer, IActionResult)> LoadAuthorizedCustomerPublic(string id) => LoadAuthorizedCustomer(id); @@ -39,7 +39,7 @@ public class TestCustomerController( CustomerManagerServiceMock.Object, CustomerAttributeParserMock.Object, CustomerAttributeServiceMock.Object, - MessageProviderServiceMock.Object, + CustomerMessageProviderServiceMock.Object, GroupServiceMock.Object, TranslationServiceMock.Object, ContextAccessorMock.Object, diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCustomerManagementControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCustomerManagementControllerTests.cs index 43272de193..f24b0b8d4b 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCustomerManagementControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCustomerManagementControllerTests.cs @@ -27,12 +27,12 @@ public class BaseCustomerManagementControllerTests private class TestManagementController( ICustomerService customerService, ICustomerViewModelService customerViewModelService, ICustomerManagerService customerManagerService, ICustomerAttributeParser customerAttributeParser, - ICustomerAttributeService customerAttributeService, IMessageProviderService messageProviderService, + ICustomerAttributeService customerAttributeService, ICustomerMessageProviderService customerMessageProviderService, IGroupService groupService, IRequestTranslationService translationService, IContextAccessor contextAccessor, CustomerSettings customerSettings, IAdminDataScope scope, IRequestPermissionService permissionService, IExportManager exportManager) : BaseCustomerManagementController(customerService, customerViewModelService, customerManagerService, - customerAttributeParser, customerAttributeService, messageProviderService, groupService, + customerAttributeParser, customerAttributeService, customerMessageProviderService, groupService, translationService, contextAccessor, customerSettings, scope, permissionService, exportManager) { public void CheckTwoFactorEnabledWarningPublic(Customer existingCustomer, CustomerModel model) => @@ -64,7 +64,7 @@ public void Setup() _controller = new TestManagementController(_customerServiceMock.Object, new Mock().Object, new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, _groupServiceMock.Object, translationServiceMock.Object, + new Mock().Object, _groupServiceMock.Object, translationServiceMock.Object, _contextAccessorMock.Object, new CustomerSettings(), scopeMock.Object, _permissionServiceMock.Object, new Mock>().Object); diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/CustomerControllerTestsBase.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/CustomerControllerTestsBase.cs index ea932900cf..0c0fdfd62e 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/CustomerControllerTestsBase.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/CustomerControllerTestsBase.cs @@ -47,7 +47,7 @@ public abstract class CustomerControllerTestsBase where TController protected Mock CustomerAttributeServiceMock; protected Mock AddressAttributeParserMock; protected Mock AddressAttributeServiceMock; - protected Mock MessageProviderServiceMock; + protected Mock CustomerMessageProviderServiceMock; protected Mock GroupServiceMock; protected Mock TranslationServiceMock; protected Mock ContextAccessorMock; @@ -80,7 +80,7 @@ public void Setup() AddressAttributeServiceMock = new Mock(); AddressAttributeServiceMock.Setup(a => a.GetAllAddressAttributes()) .ReturnsAsync(new List()); - MessageProviderServiceMock = new Mock(); + CustomerMessageProviderServiceMock = new Mock(); GroupServiceMock = new Mock(); TranslationServiceMock = new Mock(); TranslationServiceMock.Setup(t => t.GetResource(It.IsAny())).Returns("resource"); diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/LoginControllerExternalSignInTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/LoginControllerExternalSignInTests.cs index aff51054d2..49cf1272c9 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/LoginControllerExternalSignInTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/LoginControllerExternalSignInTests.cs @@ -35,7 +35,7 @@ public void Setup() _controller = new LoginController(new CustomerSettings(), new CaptchaSettings(), new Mock().Object, new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock { DefaultValue = DefaultValue.Mock }.Object, + new Mock().Object, new Mock { DefaultValue = DefaultValue.Mock }.Object, new Mock().Object); var httpContext = new DefaultHttpContext(); httpContext.Request.PathBase = "/shop"; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/LoginControllerProviderOnlyTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/LoginControllerProviderOnlyTests.cs index f46550f2cb..ec7ee28f8d 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/LoginControllerProviderOnlyTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/LoginControllerProviderOnlyTests.cs @@ -45,7 +45,7 @@ private LoginController CreateController(string area) var controller = new LoginController(new CustomerSettings(), new CaptchaSettings(), new Mock().Object, _customerManagerService.Object, new Mock().Object, new Mock().Object, - new Mock().Object, _contextAccessor.Object, + new Mock().Object, _contextAccessor.Object, new Mock().Object); var httpContext = new DefaultHttpContext(); httpContext.Request.PathBase = "/shop"; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/MerchandiseReturnViewModelServiceTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/MerchandiseReturnViewModelServiceTests.cs index f1c2bbfb1b..de2dd7690d 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/MerchandiseReturnViewModelServiceTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/MerchandiseReturnViewModelServiceTests.cs @@ -122,7 +122,7 @@ private static MerchandiseReturnViewModelService Build(ICustomerService customer var dateTimeServiceMock = new Mock(); dateTimeServiceMock.Setup(d => d.CurrentTimeZone).Returns(TimeZoneInfo.Utc); var translationServiceMock = new Mock(); - var messageProviderServiceMock = new Mock(); + var merchandiseReturnMessageProviderServiceMock = new Mock(); var languageSettings = new LanguageSettings(); var merchandiseReturnServiceMock = new Mock(); merchandiseReturnServiceMock @@ -149,7 +149,7 @@ private static MerchandiseReturnViewModelService Build(ICustomerService customer customerService, dateTimeServiceMock.Object, translationServiceMock.Object, - messageProviderServiceMock.Object, + merchandiseReturnMessageProviderServiceMock.Object, languageSettings, merchandiseReturnServiceMock.Object, priceFormatterMock.Object, @@ -176,7 +176,7 @@ private static MerchandiseReturnViewModelService BuildWithCustomMocks( var dateTimeServiceMock = new Mock(); dateTimeServiceMock.Setup(d => d.CurrentTimeZone).Returns(TimeZoneInfo.Utc); var translationServiceMock = new Mock(); - var messageProviderServiceMock = new Mock(); + var merchandiseReturnMessageProviderServiceMock = new Mock(); var languageSettings = new LanguageSettings(); var priceFormatterMock = new Mock(); var addressSettings = new AddressSettings(); @@ -194,7 +194,7 @@ private static MerchandiseReturnViewModelService BuildWithCustomMocks( customerServiceMock.Object, dateTimeServiceMock.Object, translationServiceMock.Object, - messageProviderServiceMock.Object, + merchandiseReturnMessageProviderServiceMock.Object, languageSettings, merchandiseReturnService, priceFormatterMock.Object, diff --git a/src/Tests/Grand.Web.Admin.Tests/Services/CustomerNoteViewModelServiceTests.cs b/src/Tests/Grand.Web.Admin.Tests/Services/CustomerNoteViewModelServiceTests.cs index f38f7afcd9..c124fd062c 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Services/CustomerNoteViewModelServiceTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Services/CustomerNoteViewModelServiceTests.cs @@ -31,7 +31,7 @@ public void Setup() _customerNoteServiceMock.Object, new Mock().Object, _downloadServiceMock.Object, - new Mock().Object, + new Mock().Object, new Mock().Object, new Mock().Object); } diff --git a/src/Tests/Grand.Web.Admin.Tests/Services/PanelPasswordRecoveryServiceTests.cs b/src/Tests/Grand.Web.Admin.Tests/Services/PanelPasswordRecoveryServiceTests.cs index 881588f89f..24c7554771 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Services/PanelPasswordRecoveryServiceTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Services/PanelPasswordRecoveryServiceTests.cs @@ -31,7 +31,7 @@ public class PanelPasswordRecoveryServiceTests private Mock _groupService; private Mock _permissionService; private Mock _vendorService; - private Mock _messageProviderService; + private Mock _customerMessageProviderService; private Mock _signInPolicy; private CustomerSettings _customerSettings; private Customer _customer; @@ -49,7 +49,7 @@ public void Init() _groupService.Setup(x => x.IsRegistered(_customer)).ReturnsAsync(true); _permissionService = new Mock(); _vendorService = new Mock(); - _messageProviderService = new Mock(); + _customerMessageProviderService = new Mock(); _signInPolicy = new Mock(); _signInPolicy.Setup(x => x.PasswordSignIn(It.IsAny(), It.IsAny())) .ReturnsAsync(PasswordSignInDecision.Password); @@ -69,7 +69,7 @@ public void Init() _service = new PanelPasswordRecoveryService(_customerService.Object, _customerManagerService.Object, _historyService.Object, _groupService.Object, _permissionService.Object, _vendorService.Object, - _messageProviderService.Object, translationService.Object, contextAccessor.Object, _customerSettings, + _customerMessageProviderService.Object, translationService.Object, contextAccessor.Object, _customerSettings, _signInPolicy.Object); } @@ -108,7 +108,7 @@ private void GiveToken(DateTime generatedUtc) private void VerifyMessageSent(Times times) { - _messageProviderService.Verify(x => x.SendCustomerPasswordRecoveryMessage( + _customerMessageProviderService.Verify(x => x.SendCustomerPasswordRecoveryMessage( It.IsAny(), It.IsAny(), It.IsAny()), times); } @@ -123,7 +123,7 @@ public async Task SendRecoveryMessage_AdminOfAdminPanel_StoresTokenAndArea_Sends It.Is(t => t.Length == 36), ""), Times.Once); _customerService.Verify(x => x.UpdateUserField(_customer, SystemCustomerFieldNames.PasswordRecoveryArea, "admin", ""), Times.Once); - _messageProviderService.Verify(x => x.SendCustomerPasswordRecoveryMessage(_customer, + _customerMessageProviderService.Verify(x => x.SendCustomerPasswordRecoveryMessage(_customer, It.Is(s => s.Id == "store"), "lang"), Times.Once); } diff --git a/src/Tests/Grand.Web.Store.Tests/Controllers/CustomerControllerTests.cs b/src/Tests/Grand.Web.Store.Tests/Controllers/CustomerControllerTests.cs index 9fe50e6201..3b57d73699 100644 --- a/src/Tests/Grand.Web.Store.Tests/Controllers/CustomerControllerTests.cs +++ b/src/Tests/Grand.Web.Store.Tests/Controllers/CustomerControllerTests.cs @@ -69,14 +69,14 @@ private class TestableCustomerController( ICustomerManagerService customerManagerService, ICustomerAttributeParser customerAttributeParser, ICustomerAttributeService customerAttributeService, - IMessageProviderService messageProviderService, + ICustomerMessageProviderService customerMessageProviderService, IGroupService groupService, IRequestTranslationService translationService, IContextAccessor contextAccessor, CustomerSettings customerSettings, IAdminDataScope scope) : CustomerController(customerService, customerViewModelService, customerManagerService, - customerAttributeParser, customerAttributeService, messageProviderService, groupService, + customerAttributeParser, customerAttributeService, customerMessageProviderService, groupService, translationService, contextAccessor, customerSettings, scope) { public Task ApplyPostConstraintsPublic(CustomerModel model) => ApplyPostConstraints(model); @@ -91,7 +91,7 @@ private TestableCustomerController BuildStoreController(IGroupService groupServi _customerManagerServiceMock.Object, new Mock().Object, _customerAttributeServiceMock.Object, - new Mock().Object, + new Mock().Object, groupService ?? _groupServiceMock.Object, _translationServiceMock.Object, _contextAccessorMock.Object, diff --git a/src/Tests/Grand.Web.Tests/Commands/Customers/PasswordRecoverySendCommandHandlerTests.cs b/src/Tests/Grand.Web.Tests/Commands/Customers/PasswordRecoverySendCommandHandlerTests.cs index a8c721e882..5e6cb12093 100644 --- a/src/Tests/Grand.Web.Tests/Commands/Customers/PasswordRecoverySendCommandHandlerTests.cs +++ b/src/Tests/Grand.Web.Tests/Commands/Customers/PasswordRecoverySendCommandHandlerTests.cs @@ -18,7 +18,7 @@ public class PasswordRecoverySendCommandHandlerTests public async Task ExternalSignInRequired_NoMessage_NoToken() { var customerService = new Mock(); - var messages = new Mock(); + var messages = new Mock(); var policy = new Mock(); var customer = new Customer { Id = "c1" }; policy.Setup(x => x.PasswordSignIn(customer, It.IsAny())).ReturnsAsync(new PasswordSignInDecision(false, new Mock().Object)); @@ -35,7 +35,7 @@ public async Task ExternalSignInRequired_NoMessage_NoToken() [TestMethod] public async Task NullCustomer_NoMessage() { - var messages = new Mock(); + var messages = new Mock(); var handler = new PasswordRecoverySendCommandHandler(new Mock().Object, messages.Object, new Mock().Object); @@ -49,7 +49,7 @@ public async Task NullCustomer_NoMessage() [TestMethod] public async Task PasswordAllowed_MessageSent() { - var messages = new Mock(); + var messages = new Mock(); var policy = new Mock(); policy.Setup(x => x.PasswordSignIn(It.IsAny(), SignInSurface.Storefront)) .ReturnsAsync(PasswordSignInDecision.Password); diff --git a/src/Tests/Grand.Web.Tests/Controllers/AccountControllerExternalRegisterTests.cs b/src/Tests/Grand.Web.Tests/Controllers/AccountControllerExternalRegisterTests.cs index 7f9d5d0f0e..c1e895d28a 100644 --- a/src/Tests/Grand.Web.Tests/Controllers/AccountControllerExternalRegisterTests.cs +++ b/src/Tests/Grand.Web.Tests/Controllers/AccountControllerExternalRegisterTests.cs @@ -32,7 +32,7 @@ public class AccountControllerExternalRegisterTests new("ExternalAuth.OpenIdConnect", "sub-1", "john@example.com", true, "John", "Doe"); private Mock _manager; - private Mock _messages; + private Mock _messages; private Mock _external; private Mock _externalAuth; private CustomerSettings _settings; @@ -43,7 +43,7 @@ public class AccountControllerExternalRegisterTests public void Init() { _manager = new Mock(); - _messages = new Mock(); + _messages = new Mock(); _external = new Mock(); _externalAuth = new Mock(); _settings = new CustomerSettings { UserRegistrationType = UserRegistrationType.Standard }; diff --git a/src/Tests/Grand.Web.Tests/Controllers/AccountControllerProviderOnlyTests.cs b/src/Tests/Grand.Web.Tests/Controllers/AccountControllerProviderOnlyTests.cs index 45fe8eff2f..355552f57f 100644 --- a/src/Tests/Grand.Web.Tests/Controllers/AccountControllerProviderOnlyTests.cs +++ b/src/Tests/Grand.Web.Tests/Controllers/AccountControllerProviderOnlyTests.cs @@ -73,7 +73,7 @@ public void Init() _controller = new AccountController(new Mock().Object, new Mock().Object, context.Object, _customers.Object, _groups.Object, _customerManager.Object, new Mock().Object, - mediator.Object, new Mock().Object, new CaptchaSettings(), + mediator.Object, new Mock().Object, new CaptchaSettings(), _customerSettings = new CustomerSettings { UserRegistrationType = UserRegistrationType.Standard, TwoFactorAuthenticationEnabled = true, AllowUsersToDeleteAccount = true diff --git a/src/Tests/Grand.Web.Tests/Controllers/AccountControllerTwoFactorReturnUrlTests.cs b/src/Tests/Grand.Web.Tests/Controllers/AccountControllerTwoFactorReturnUrlTests.cs index 4330022878..92d1e1a414 100644 --- a/src/Tests/Grand.Web.Tests/Controllers/AccountControllerTwoFactorReturnUrlTests.cs +++ b/src/Tests/Grand.Web.Tests/Controllers/AccountControllerTwoFactorReturnUrlTests.cs @@ -55,7 +55,7 @@ public void Init() var settings = new CustomerSettings { TwoFactorAuthenticationEnabled = true }; _controller = new AccountController(new Mock().Object, new Mock().Object, context.Object, _customers.Object, new Mock().Object, _manager.Object, new Mock().Object, - new Mock().Object, new Mock().Object, new CaptchaSettings(), settings, + new Mock().Object, new Mock().Object, new CaptchaSettings(), settings, new CustomerConfig()); var http = new DefaultHttpContext { Session = new TestSession() }; _controller.ControllerContext = new ControllerContext { HttpContext = http }; diff --git a/src/Tests/Grand.Web.Tests/Controllers/ContentPreviewTests.cs b/src/Tests/Grand.Web.Tests/Controllers/ContentPreviewTests.cs index fc500e5a70..de010d645a 100644 --- a/src/Tests/Grand.Web.Tests/Controllers/ContentPreviewTests.cs +++ b/src/Tests/Grand.Web.Tests/Controllers/ContentPreviewTests.cs @@ -108,7 +108,7 @@ private KnowledgebaseController KnowledgebaseController(KnowledgebaseArticle art return new KnowledgebaseController(new KnowledgebaseSettings { Enabled = true }, knowledgebaseServiceMock.Object, _contextAccessorMock.Object, new Mock().Object, _aclServiceMock.Object, new Mock().Object, - new Mock().Object, new Mock().Object, + new Mock().Object, new Mock().Object, _permissionServiceMock.Object, new CustomerSettings(), new CaptchaSettings(), new LanguageSettings()) { ControllerContext = GetContext(new ServiceCollection() .AddSingleton(new Mock().Object) diff --git a/src/Web/Grand.Web.Admin/Controllers/CustomerController.cs b/src/Web/Grand.Web.Admin/Controllers/CustomerController.cs index f6ac4bcbe7..ead49f6238 100644 --- a/src/Web/Grand.Web.Admin/Controllers/CustomerController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/CustomerController.cs @@ -17,7 +17,7 @@ public class CustomerController( Grand.Business.Core.Interfaces.Customers.ICustomerManagerService customerManagerService, Grand.Business.Core.Interfaces.Customers.ICustomerAttributeParser customerAttributeParser, Grand.Business.Core.Interfaces.Customers.ICustomerAttributeService customerAttributeService, - Grand.Business.Core.Interfaces.Messages.IMessageProviderService messageProviderService, + Grand.Business.Core.Interfaces.Messages.ICustomerMessageProviderService customerMessageProviderService, Grand.Business.Core.Interfaces.Common.Directory.IGroupService groupService, Grand.Web.Common.Localization.IRequestTranslationService translationService, Grand.Infrastructure.IContextAccessor contextAccessor, @@ -26,5 +26,5 @@ public class CustomerController( IRequestPermissionService permissionService, Grand.Business.Core.Interfaces.ExportImport.IExportManager exportManager) : BaseCustomerManagementController(customerService, customerViewModelService, customerManagerService, - customerAttributeParser, customerAttributeService, messageProviderService, groupService, + customerAttributeParser, customerAttributeService, customerMessageProviderService, groupService, translationService, contextAccessor, customerSettings, scope, permissionService, exportManager); diff --git a/src/Web/Grand.Web.Admin/Controllers/LoginController.cs b/src/Web/Grand.Web.Admin/Controllers/LoginController.cs index ed8a66edd1..6e84c936fa 100644 --- a/src/Web/Grand.Web.Admin/Controllers/LoginController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/LoginController.cs @@ -15,7 +15,7 @@ namespace Grand.Web.Admin.Controllers; [Area(Constants.AreaAdmin)] public class LoginController : BaseLoginController { - public LoginController(CustomerSettings customerSettings, CaptchaSettings captchaSettings, IRequestTranslationService translationService, ICustomerManagerService customerManagerService, ICustomerService customerService, IGrandAuthenticationService authenticationService, IMessageProviderService messageProviderService, IContextAccessor contextAccessor, IMediator mediator) : base(customerSettings, captchaSettings, translationService, customerManagerService, customerService, authenticationService, messageProviderService, contextAccessor, mediator) + public LoginController(CustomerSettings customerSettings, CaptchaSettings captchaSettings, IRequestTranslationService translationService, ICustomerManagerService customerManagerService, ICustomerService customerService, IGrandAuthenticationService authenticationService, ICustomerMessageProviderService customerMessageProviderService, IContextAccessor contextAccessor, IMediator mediator) : base(customerSettings, captchaSettings, translationService, customerManagerService, customerService, authenticationService, customerMessageProviderService, contextAccessor, mediator) { } } diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseCustomerController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseCustomerController.cs index 215a1f0eb1..0f56dd20a1 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseCustomerController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseCustomerController.cs @@ -31,7 +31,7 @@ public abstract class BaseCustomerController( ICustomerManagerService customerManagerService, ICustomerAttributeParser customerAttributeParser, ICustomerAttributeService customerAttributeService, - IMessageProviderService messageProviderService, + ICustomerMessageProviderService customerMessageProviderService, IGroupService groupService, IRequestTranslationService translationService, IContextAccessor contextAccessor, @@ -333,7 +333,7 @@ public async Task SendWelcomeMessage(string id) var (customer, denied) = await LoadAuthorizedCustomer(id); if (customer is null) return denied!; - await messageProviderService.SendCustomerWelcomeMessage(customer, contextAccessor.StoreContext.CurrentStore, + await customerMessageProviderService.SendCustomerWelcomeMessage(customer, contextAccessor.StoreContext.CurrentStore, contextAccessor.WorkContext.WorkingLanguage.Id); Success(translationService.GetResource("Admin.Customers.Customers.SendWelcomeMessage.Success")); return RedirectToAction("Edit", new { id = customer.Id }); @@ -348,7 +348,7 @@ public async Task ReSendActivationMessage(string id) await CustomerService.UpdateUserField(customer, SystemCustomerFieldNames.AccountActivationToken, Guid.NewGuid().ToString()); - await messageProviderService.SendCustomerEmailValidationMessage(customer, + await customerMessageProviderService.SendCustomerEmailValidationMessage(customer, contextAccessor.StoreContext.CurrentStore, contextAccessor.WorkContext.WorkingLanguage.Id); Success(translationService.GetResource("Admin.Customers.Customers.ReSendActivationMessage.Success")); return RedirectToAction("Edit", new { id = customer.Id }); diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseCustomerManagementController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseCustomerManagementController.cs index 6b030f2e6d..580e4ded12 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseCustomerManagementController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseCustomerManagementController.cs @@ -22,7 +22,7 @@ public abstract class BaseCustomerManagementController( ICustomerManagerService customerManagerService, ICustomerAttributeParser customerAttributeParser, ICustomerAttributeService customerAttributeService, - IMessageProviderService messageProviderService, + ICustomerMessageProviderService customerMessageProviderService, IGroupService groupService, IRequestTranslationService translationService, IContextAccessor contextAccessor, @@ -31,7 +31,7 @@ public abstract class BaseCustomerManagementController( IRequestPermissionService permissionService, IExportManager exportManager) : BaseCustomerController(customerService, customerViewModelService, customerManagerService, - customerAttributeParser, customerAttributeService, messageProviderService, groupService, + customerAttributeParser, customerAttributeService, customerMessageProviderService, groupService, translationService, contextAccessor, customerSettings, scope) { protected override void CheckTwoFactorEnabledWarning(Customer? existingCustomer, CustomerModel model) diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseLoginController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseLoginController.cs index 21333964de..6908e35070 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseLoginController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseLoginController.cs @@ -27,7 +27,7 @@ public abstract class BaseLoginController : BaseController private readonly ICustomerService _customerService; private readonly CustomerSettings _customerSettings; private readonly IMediator _mediator; - private readonly IMessageProviderService _messageProviderService; + private readonly ICustomerMessageProviderService _customerMessageProviderService; private readonly IRequestTranslationService _translationService; private readonly IContextAccessor _contextAccessor; @@ -38,7 +38,7 @@ public BaseLoginController( ICustomerManagerService customerManagerService, ICustomerService customerService, IGrandAuthenticationService authenticationService, - IMessageProviderService messageProviderService, + ICustomerMessageProviderService customerMessageProviderService, IContextAccessor contextAccessor, IMediator mediator) { @@ -48,7 +48,7 @@ public BaseLoginController( _customerManagerService = customerManagerService; _customerService = customerService; _authenticationService = authenticationService; - _messageProviderService = messageProviderService; + _customerMessageProviderService = customerMessageProviderService; _contextAccessor = contextAccessor; _mediator = mediator; } @@ -254,7 +254,7 @@ await twoFactorAuthenticationService.GenerateCodeSetup("", customer, _contextAccessor.StoreContext.CurrentStore, _contextAccessor.WorkContext.WorkingLanguage, _customerSettings.TwoFactorAuthenticationType); if (_customerSettings.TwoFactorAuthenticationType == TwoFactorAuthenticationType.EmailVerification) - await _messageProviderService.SendCustomerEmailTokenValidationMessage(customer, + await _customerMessageProviderService.SendCustomerEmailTokenValidationMessage(customer, _contextAccessor.StoreContext.CurrentStore, _contextAccessor.WorkContext.WorkingLanguage.Id); } diff --git a/src/Web/Grand.Web.AdminShared/Services/CustomerNoteViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/CustomerNoteViewModelService.cs index 49841a145b..5d28d52e79 100644 --- a/src/Web/Grand.Web.AdminShared/Services/CustomerNoteViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/CustomerNoteViewModelService.cs @@ -13,7 +13,7 @@ public class CustomerNoteViewModelService( ICustomerNoteService customerNoteService, ICustomerService customerService, IDownloadService downloadService, - IMessageProviderService messageProviderService, + ICustomerMessageProviderService customerMessageProviderService, IDateTimeService dateTimeService, IContextAccessor contextAccessor) : ICustomerNoteViewModelService @@ -58,7 +58,7 @@ public virtual async Task InsertCustomerNote(string customerId, st if (displayToCustomer) { //email - await messageProviderService.SendNewCustomerNoteMessage(customerNote, + await customerMessageProviderService.SendNewCustomerNoteMessage(customerNote, await customerService.GetCustomerById(customerId), contextAccessor.StoreContext.CurrentStore, contextAccessor.WorkContext.WorkingLanguage.Id); } diff --git a/src/Web/Grand.Web.AdminShared/Services/GiftVoucherViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/GiftVoucherViewModelService.cs index 0112652f59..4722cca0c6 100644 --- a/src/Web/Grand.Web.AdminShared/Services/GiftVoucherViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/GiftVoucherViewModelService.cs @@ -22,7 +22,7 @@ public class GiftVoucherViewModelService : IGiftVoucherViewModelService public GiftVoucherViewModelService( IGiftVoucherService giftVoucherService, IOrderService orderService, - IPriceFormatter priceFormatter, IMessageProviderService messageProviderService, + IPriceFormatter priceFormatter, IOrderMessageProviderService orderMessageProviderService, IDateTimeService dateTimeService, ICurrencyService currencyService, LanguageSettings languageSettings, @@ -33,7 +33,7 @@ public GiftVoucherViewModelService( _giftVoucherService = giftVoucherService; _orderService = orderService; _priceFormatter = priceFormatter; - _messageProviderService = messageProviderService; + _orderMessageProviderService = orderMessageProviderService; _dateTimeService = dateTimeService; _currencyService = currencyService; _languageSettings = languageSettings; @@ -163,7 +163,7 @@ public virtual async Task NotifyRecipient(GiftVoucher giftVoucher) languageId = _languageSettings.DefaultAdminLanguageId; } - var queuedEmailId = await _messageProviderService.SendGiftVoucherMessage(giftVoucher, order, languageId); + var queuedEmailId = await _orderMessageProviderService.SendGiftVoucherMessage(giftVoucher, order, languageId); if (queuedEmailId > 0) { giftVoucher.IsRecipientNotified = true; @@ -232,7 +232,7 @@ public virtual async private readonly IGiftVoucherService _giftVoucherService; private readonly IOrderService _orderService; private readonly IPriceFormatter _priceFormatter; - private readonly IMessageProviderService _messageProviderService; + private readonly IOrderMessageProviderService _orderMessageProviderService; private readonly IDateTimeService _dateTimeService; private readonly ICurrencyService _currencyService; private readonly LanguageSettings _languageSettings; diff --git a/src/Web/Grand.Web.AdminShared/Services/MerchandiseReturnViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/MerchandiseReturnViewModelService.cs index 10c0477b34..251a122b3f 100644 --- a/src/Web/Grand.Web.AdminShared/Services/MerchandiseReturnViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/MerchandiseReturnViewModelService.cs @@ -27,7 +27,7 @@ public class MerchandiseReturnViewModelService( ICustomerService customerService, IDateTimeService dateTimeService, IRequestTranslationService translationService, - IMessageProviderService messageProviderService, + IMerchandiseReturnMessageProviderService merchandiseReturnMessageProviderService, LanguageSettings languageSettings, IMerchandiseReturnService merchandiseReturnService, IPriceFormatter priceFormatter, @@ -197,7 +197,7 @@ public virtual async Task PrepareAddressModel(AddressModel model, public virtual async Task NotifyCustomer(MerchandiseReturn merchandiseReturn) { var order = await orderService.GetOrderById(merchandiseReturn.OrderId); - await messageProviderService.SendMerchandiseReturnStatusChangedCustomerMessage(merchandiseReturn, order, + await merchandiseReturnMessageProviderService.SendMerchandiseReturnStatusChangedCustomerMessage(merchandiseReturn, order, languageSettings.DefaultAdminLanguageId); } @@ -309,7 +309,7 @@ public virtual async Task InsertMerchandiseReturnNote(MerchandiseReturn merchand //new merchandise return notification if (displayToCustomer) //email - await messageProviderService.SendNewMerchandiseReturnNoteAddedCustomerMessage(merchandiseReturn, + await merchandiseReturnMessageProviderService.SendNewMerchandiseReturnNoteAddedCustomerMessage(merchandiseReturn, merchandiseReturnNote, order); } diff --git a/src/Web/Grand.Web.AdminShared/Services/OrderViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/OrderViewModelService.cs index dbe5749b78..4e42c1f2ab 100644 --- a/src/Web/Grand.Web.AdminShared/Services/OrderViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/OrderViewModelService.cs @@ -56,7 +56,7 @@ public class OrderViewModelService : IOrderViewModelService private readonly IPaymentTransactionService _paymentTransactionService; private readonly ICountryService _countryService; private readonly IProductService _productService; - private readonly IMessageProviderService _messageProviderService; + private readonly IOrderMessageProviderService _orderMessageProviderService; private readonly IProductAttributeService _productAttributeService; private readonly IGiftVoucherService _giftVoucherService; private readonly IDownloadService _downloadService; @@ -99,7 +99,7 @@ public OrderViewModelService(IOrderService orderService, IPaymentTransactionService paymentTransactionService, ICountryService countryService, IProductService productService, - IMessageProviderService messageProviderService, + IOrderMessageProviderService orderMessageProviderService, IProductAttributeService productAttributeService, IGiftVoucherService giftVoucherService, IDownloadService downloadService, @@ -137,7 +137,7 @@ public OrderViewModelService(IOrderService orderService, _paymentTransactionService = paymentTransactionService; _countryService = countryService; _productService = productService; - _messageProviderService = messageProviderService; + _orderMessageProviderService = orderMessageProviderService; _productAttributeService = productAttributeService; _giftVoucherService = giftVoucherService; _downloadService = downloadService; @@ -1025,7 +1025,7 @@ public virtual async Task InsertOrderNote(Order order, string downloadId, bool d //new order notification if (displayToCustomer) //email - await _messageProviderService.SendNewOrderNoteAddedCustomerMessage(order, orderNote); + await _orderMessageProviderService.SendNewOrderNoteAddedCustomerMessage(order, orderNote); } public virtual async Task DeleteOrderNote(Order order, string id) diff --git a/src/Web/Grand.Web.AdminShared/Services/PanelPasswordRecoveryService.cs b/src/Web/Grand.Web.AdminShared/Services/PanelPasswordRecoveryService.cs index bddf0f5c1e..f3c432c12d 100644 --- a/src/Web/Grand.Web.AdminShared/Services/PanelPasswordRecoveryService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/PanelPasswordRecoveryService.cs @@ -20,7 +20,7 @@ public class PanelPasswordRecoveryService( IGroupService groupService, IPermissionService permissionService, IVendorService vendorService, - IMessageProviderService messageProviderService, + ICustomerMessageProviderService customerMessageProviderService, IRequestTranslationService translationService, IContextAccessor contextAccessor, CustomerSettings customerSettings, @@ -44,7 +44,7 @@ await customerService.UpdateUserField(customer, SystemCustomerFieldNames.Passwor await customerService.UpdateUserField(customer, SystemCustomerFieldNames.PasswordRecoveryArea, area.ToLowerInvariant()); - await messageProviderService.SendCustomerPasswordRecoveryMessage(customer, + await customerMessageProviderService.SendCustomerPasswordRecoveryMessage(customer, contextAccessor.StoreContext.CurrentStore, contextAccessor.WorkContext.WorkingLanguage.Id); } diff --git a/src/Web/Grand.Web.Store/Controllers/CustomerController.cs b/src/Web/Grand.Web.Store/Controllers/CustomerController.cs index a5e943869b..730314c433 100644 --- a/src/Web/Grand.Web.Store/Controllers/CustomerController.cs +++ b/src/Web/Grand.Web.Store/Controllers/CustomerController.cs @@ -30,14 +30,14 @@ public class CustomerController( Grand.Business.Core.Interfaces.Customers.ICustomerManagerService customerManagerService, Grand.Business.Core.Interfaces.Customers.ICustomerAttributeParser customerAttributeParser, Grand.Business.Core.Interfaces.Customers.ICustomerAttributeService customerAttributeService, - Grand.Business.Core.Interfaces.Messages.IMessageProviderService messageProviderService, + Grand.Business.Core.Interfaces.Messages.ICustomerMessageProviderService customerMessageProviderService, IGroupService groupService, Grand.Web.Common.Localization.IRequestTranslationService translationService, IContextAccessor contextAccessor, CustomerSettings customerSettings, IAdminDataScope scope) : BaseCustomerController(customerService, customerViewModelService, customerManagerService, - customerAttributeParser, customerAttributeService, messageProviderService, groupService, + customerAttributeParser, customerAttributeService, customerMessageProviderService, groupService, translationService, contextAccessor, customerSettings, scope) { /// Shown instead of the panel when per-store customer identity is disabled. diff --git a/src/Web/Grand.Web.Store/Controllers/LoginController.cs b/src/Web/Grand.Web.Store/Controllers/LoginController.cs index 667be5ad1b..4cc7b136cd 100644 --- a/src/Web/Grand.Web.Store/Controllers/LoginController.cs +++ b/src/Web/Grand.Web.Store/Controllers/LoginController.cs @@ -15,7 +15,7 @@ namespace Grand.Web.Store.Controllers; [Area(Constants.AreaStore)] public class LoginController : BaseLoginController { - public LoginController(CustomerSettings customerSettings, CaptchaSettings captchaSettings, IRequestTranslationService translationService, ICustomerManagerService customerManagerService, ICustomerService customerService, IGrandAuthenticationService authenticationService, IMessageProviderService messageProviderService, IContextAccessor contextAccessor, IMediator mediator) : base(customerSettings, captchaSettings, translationService, customerManagerService, customerService, authenticationService, messageProviderService, contextAccessor, mediator) + public LoginController(CustomerSettings customerSettings, CaptchaSettings captchaSettings, IRequestTranslationService translationService, ICustomerManagerService customerManagerService, ICustomerService customerService, IGrandAuthenticationService authenticationService, ICustomerMessageProviderService customerMessageProviderService, IContextAccessor contextAccessor, IMediator mediator) : base(customerSettings, captchaSettings, translationService, customerManagerService, customerService, authenticationService, customerMessageProviderService, contextAccessor, mediator) { } } diff --git a/src/Web/Grand.Web.Vendor/Controllers/LoginController.cs b/src/Web/Grand.Web.Vendor/Controllers/LoginController.cs index 4aef6cbdee..18680d3c99 100644 --- a/src/Web/Grand.Web.Vendor/Controllers/LoginController.cs +++ b/src/Web/Grand.Web.Vendor/Controllers/LoginController.cs @@ -15,7 +15,7 @@ namespace Grand.Web.Vendor.Controllers; [Area(Constants.AreaVendor)] public class LoginController : BaseLoginController { - public LoginController(CustomerSettings customerSettings, CaptchaSettings captchaSettings, IRequestTranslationService translationService, ICustomerManagerService customerManagerService, ICustomerService customerService, IGrandAuthenticationService authenticationService, IMessageProviderService messageProviderService, IContextAccessor contextAccessor, IMediator mediator) : base(customerSettings, captchaSettings, translationService, customerManagerService, customerService, authenticationService, messageProviderService, contextAccessor, mediator) + public LoginController(CustomerSettings customerSettings, CaptchaSettings captchaSettings, IRequestTranslationService translationService, ICustomerManagerService customerManagerService, ICustomerService customerService, IGrandAuthenticationService authenticationService, ICustomerMessageProviderService customerMessageProviderService, IContextAccessor contextAccessor, IMediator mediator) : base(customerSettings, captchaSettings, translationService, customerManagerService, customerService, authenticationService, customerMessageProviderService, contextAccessor, mediator) { } } diff --git a/src/Web/Grand.Web/Commands/Handler/Blogs/InsertBlogCommentCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/Blogs/InsertBlogCommentCommandHandler.cs index b727efdaa8..55c409874e 100644 --- a/src/Web/Grand.Web/Commands/Handler/Blogs/InsertBlogCommentCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Blogs/InsertBlogCommentCommandHandler.cs @@ -16,17 +16,17 @@ public class InsertBlogCommentCommandHandler : IRequestHandler Handle(InsertBlogCommentCommand request, Cancella if (!customer.HasContributions) await _customerService.UpdateContributions(customer); //notify a store owner if (_blogSettings.NotifyAboutNewBlogComments) - await _messageProviderService.SendBlogCommentMessage(request.BlogPost, comment, + await _cmsMessageProviderService.SendBlogCommentMessage(request.BlogPost, comment, _languageSettings.DefaultAdminLanguageId, host: _contextAccessor.StoreContext.CurrentHost); return comment; diff --git a/src/Web/Grand.Web/Commands/Handler/Contact/ContactUsSendCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/Contact/ContactUsSendCommandHandler.cs index e92788fa0e..5d20544a33 100644 --- a/src/Web/Grand.Web/Commands/Handler/Contact/ContactUsSendCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Contact/ContactUsSendCommandHandler.cs @@ -15,18 +15,18 @@ public class ContactUsSendCommandHandler : IRequestHandler SendContactUs(ContactUsSendCommand request) var subject = _commonSettings.SubjectFieldOnContactUsForm ? request.Model.Subject : null; var body = FormatText.ConvertText(request.Model.Enquiry); - await _messageProviderService.SendContactUsMessage + await _customerMessageProviderService.SendContactUsMessage (request.Customer, request.Store, request.Language.Id, request.Model.Email.Trim(), request.Model.FullName, subject, body, request.Model.ContactAttributeInfo, request.Model.ContactAttribute, request.IpAddress, diff --git a/src/Web/Grand.Web/Commands/Handler/Customers/CustomerRegisteredCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/Customers/CustomerRegisteredCommandHandler.cs index f69a4aa29b..25da2864b2 100644 --- a/src/Web/Grand.Web/Commands/Handler/Customers/CustomerRegisteredCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Customers/CustomerRegisteredCommandHandler.cs @@ -23,14 +23,14 @@ public class CustomerRegisteredCommandHandler : IRequestHandler Handle(DeleteAccountCommand request, CancellationToken cancellationToken) { //send notification to customer - await _messageProviderService.SendCustomerDeleteStoreOwnerMessage(request.Customer, + await _customerMessageProviderService.SendCustomerDeleteStoreOwnerMessage(request.Customer, _languageSettings.DefaultAdminLanguageId, host: request.Host); //delete emails diff --git a/src/Web/Grand.Web/Commands/Handler/Customers/PasswordRecoverySendCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/Customers/PasswordRecoverySendCommandHandler.cs index 50a0be2f58..ef95bd26a2 100644 --- a/src/Web/Grand.Web/Commands/Handler/Customers/PasswordRecoverySendCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Customers/PasswordRecoverySendCommandHandler.cs @@ -9,18 +9,18 @@ namespace Grand.Web.Commands.Handler.Customers; public class PasswordRecoverySendCommandHandler : IRequestHandler { - private readonly IMessageProviderService _messageProviderService; + private readonly ICustomerMessageProviderService _customerMessageProviderService; private readonly ICustomerService _customerService; private readonly IExternalSignInPolicy _externalSignInPolicy; public PasswordRecoverySendCommandHandler( ICustomerService customerService, - IMessageProviderService messageProviderService, + ICustomerMessageProviderService customerMessageProviderService, IExternalSignInPolicy externalSignInPolicy) { _externalSignInPolicy = externalSignInPolicy; _customerService = customerService; - _messageProviderService = messageProviderService; + _customerMessageProviderService = customerMessageProviderService; } public async Task Handle(PasswordRecoverySendCommand request, CancellationToken cancellationToken) @@ -41,7 +41,7 @@ await _customerService.UpdateUserField(request.Customer, SystemCustomerF null); //send email - await _messageProviderService.SendCustomerPasswordRecoveryMessage(request.Customer, request.Store, + await _customerMessageProviderService.SendCustomerPasswordRecoveryMessage(request.Customer, request.Store, request.Language.Id, host: request.Host); return true; diff --git a/src/Web/Grand.Web/Commands/Handler/News/InsertNewsCommentCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/News/InsertNewsCommentCommandHandler.cs index 5337d3112a..053f0a2045 100644 --- a/src/Web/Grand.Web/Commands/Handler/News/InsertNewsCommentCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/News/InsertNewsCommentCommandHandler.cs @@ -13,20 +13,20 @@ public class InsertNewsCommentCommandHandler : IRequestHandler Handle(InsertNewsCommentCommand request, Cancella //notify a store owner; if (_newsSettings.NotifyAboutNewNewsComments) - await _messageProviderService.SendNewsCommentMessage(request.NewsItem, comment, + await _cmsMessageProviderService.SendNewsCommentMessage(request.NewsItem, comment, _languageSettings.DefaultAdminLanguageId, host: _contextAccessor.StoreContext.CurrentHost); return comment; diff --git a/src/Web/Grand.Web/Commands/Handler/Newsletter/SubscribeNewsletterHandler.cs b/src/Web/Grand.Web/Commands/Handler/Newsletter/SubscribeNewsletterHandler.cs index f5926d57c4..a4950acc64 100644 --- a/src/Web/Grand.Web/Commands/Handler/Newsletter/SubscribeNewsletterHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Newsletter/SubscribeNewsletterHandler.cs @@ -13,7 +13,7 @@ namespace Grand.Web.Commands.Handler.Newsletter; public class SubscribeNewsletterHandler : IRequestHandler { - private readonly IMessageProviderService _messageProviderService; + private readonly INewsletterMessageProviderService _newsletterMessageProviderService; private readonly INewsletterCategoryService _newsletterCategoryService; private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; private readonly IRequestTranslationService _translationService; @@ -22,12 +22,12 @@ public class SubscribeNewsletterHandler : IRequestHandler { - private readonly IMessageProviderService _messageProviderService; + private readonly IOrderMessageProviderService _orderMessageProviderService; private readonly IOrderService _orderService; - public InsertOrderNoteCommandHandler(IMessageProviderService messageProviderService, IOrderService orderService) + public InsertOrderNoteCommandHandler(IOrderMessageProviderService orderMessageProviderService, IOrderService orderService) { - _messageProviderService = messageProviderService; + _orderMessageProviderService = orderMessageProviderService; _orderService = orderService; } @@ -28,7 +28,7 @@ public async Task Handle(InsertOrderNoteCommand request, Cancellation await _orderService.InsertOrderNote(orderNote); //email - await _messageProviderService.SendNewOrderNoteAddedCustomerMessage(request.Order, orderNote, + await _orderMessageProviderService.SendNewOrderNoteAddedCustomerMessage(request.Order, orderNote, host: request.Host); return orderNote; diff --git a/src/Web/Grand.Web/Commands/Handler/Orders/MerchandiseReturnSubmitCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/Orders/MerchandiseReturnSubmitCommandHandler.cs index d0ae78bf22..3ec1ed5bdf 100644 --- a/src/Web/Grand.Web/Commands/Handler/Orders/MerchandiseReturnSubmitCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Orders/MerchandiseReturnSubmitCommandHandler.cs @@ -16,7 +16,7 @@ public class MerchandiseReturnSubmitCommandHandler : IRequestHandler Handle( await _merchandiseReturnService.InsertMerchandiseReturn(rr); //notify store owner here (email) - await _messageProviderService.SendNewMerchandiseReturnStoreOwnerMessage(rr, request.Order, + await _merchandiseReturnMessageProviderService.SendNewMerchandiseReturnStoreOwnerMessage(rr, request.Order, _languageSettings.DefaultAdminLanguageId, host: _contextAccessor.StoreContext.CurrentHost); //notify customer - await _messageProviderService.SendNewMerchandiseReturnCustomerMessage(rr, request.Order, + await _merchandiseReturnMessageProviderService.SendNewMerchandiseReturnCustomerMessage(rr, request.Order, request.Order.CustomerLanguageId, host: _contextAccessor.StoreContext.CurrentHost); return rr; diff --git a/src/Web/Grand.Web/Commands/Handler/Products/InsertProductReviewCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/Products/InsertProductReviewCommandHandler.cs index 2ecce867d0..93d08a793b 100644 --- a/src/Web/Grand.Web/Commands/Handler/Products/InsertProductReviewCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Products/InsertProductReviewCommandHandler.cs @@ -17,20 +17,20 @@ public class InsertProductReviewCommandHandler : IRequestHandler Handle(InsertProductReviewCommand request, Canc //notify store owner if (_catalogSettings.NotifyStoreOwnerAboutNewProductReviews) - await _messageProviderService.SendProductReviewMessage(request.Product, productReview, request.Store, + await _catalogMessageProviderService.SendProductReviewMessage(request.Product, productReview, request.Store, _languageSettings.DefaultAdminLanguageId, host: request.Host); return productReview; diff --git a/src/Web/Grand.Web/Commands/Handler/Products/SendProductAskQuestionMessageCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/Products/SendProductAskQuestionMessageCommandHandler.cs index 51f70bac44..7c68c6524c 100644 --- a/src/Web/Grand.Web/Commands/Handler/Products/SendProductAskQuestionMessageCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Products/SendProductAskQuestionMessageCommandHandler.cs @@ -7,16 +7,16 @@ namespace Grand.Web.Commands.Handler.Products; public class SendProductAskQuestionMessageCommandHandler : IRequestHandler { - private readonly IMessageProviderService _messageProviderService; + private readonly ICatalogMessageProviderService _catalogMessageProviderService; - public SendProductAskQuestionMessageCommandHandler(IMessageProviderService messageProviderService) + public SendProductAskQuestionMessageCommandHandler(ICatalogMessageProviderService catalogMessageProviderService) { - _messageProviderService = messageProviderService; + _catalogMessageProviderService = catalogMessageProviderService; } public async Task Handle(SendProductAskQuestionMessageCommand request, CancellationToken cancellationToken) { - await _messageProviderService.SendProductQuestionMessage(request.Customer, request.Store, + await _catalogMessageProviderService.SendProductQuestionMessage(request.Customer, request.Store, request.Language.Id, request.Product, request.Model.Email, request.Model.FullName, request.Model.Phone, FormatText.ConvertText(request.Model.Message), request.RemoteIpAddress, host: request.Host); diff --git a/src/Web/Grand.Web/Commands/Handler/Products/SendProductEmailAFriendMessageCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/Products/SendProductEmailAFriendMessageCommandHandler.cs index 859c35e073..7259c03dd8 100644 --- a/src/Web/Grand.Web/Commands/Handler/Products/SendProductEmailAFriendMessageCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Products/SendProductEmailAFriendMessageCommandHandler.cs @@ -7,16 +7,16 @@ namespace Grand.Web.Commands.Handler.Products; public class SendProductEmailAFriendMessageCommandHandler : IRequestHandler { - private readonly IMessageProviderService _messageProviderService; + private readonly ICatalogMessageProviderService _catalogMessageProviderService; - public SendProductEmailAFriendMessageCommandHandler(IMessageProviderService messageProviderService) + public SendProductEmailAFriendMessageCommandHandler(ICatalogMessageProviderService catalogMessageProviderService) { - _messageProviderService = messageProviderService; + _catalogMessageProviderService = catalogMessageProviderService; } public async Task Handle(SendProductEmailAFriendMessageCommand request, CancellationToken cancellationToken) { - await _messageProviderService.SendProductEmailAFriendMessage(request.Customer, request.Store, + await _catalogMessageProviderService.SendProductEmailAFriendMessage(request.Customer, request.Store, request.Language.Id, request.Product, request.Model.YourEmailAddress, request.Model.FriendEmail, FormatText.ConvertText(request.Model.PersonalMessage), host: request.Host); diff --git a/src/Web/Grand.Web/Commands/Handler/Vendors/ContactVendorCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/Vendors/ContactVendorCommandHandler.cs index db6590f3b4..b812b435a1 100644 --- a/src/Web/Grand.Web/Commands/Handler/Vendors/ContactVendorCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Vendors/ContactVendorCommandHandler.cs @@ -12,14 +12,14 @@ namespace Grand.Web.Commands.Handler.Vendors; public class ContactVendorCommandHandler : IRequestHandler { private readonly CommonSettings _commonSettings; - private readonly IMessageProviderService _messageProviderService; + private readonly IVendorMessageProviderService _vendorMessageProviderService; private readonly IRequestTranslationService _translationService; private readonly IContextAccessor _contextAccessor; - public ContactVendorCommandHandler(IMessageProviderService messageProviderService, IContextAccessor contextAccessor, + public ContactVendorCommandHandler(IVendorMessageProviderService vendorMessageProviderService, IContextAccessor contextAccessor, IRequestTranslationService translationService, CommonSettings commonSettings) { - _messageProviderService = messageProviderService; + _vendorMessageProviderService = vendorMessageProviderService; _contextAccessor = contextAccessor; _translationService = translationService; _commonSettings = commonSettings; @@ -30,7 +30,7 @@ public async Task Handle(ContactVendorSendCommand request, C var subject = _commonSettings.SubjectFieldOnContactUsForm ? request.Model.Subject : null; var body = FormatText.ConvertText(request.Model.Enquiry); - await _messageProviderService.SendContactVendorMessage(_contextAccessor.WorkContext.CurrentCustomer, request.Store, + await _vendorMessageProviderService.SendContactVendorMessage(_contextAccessor.WorkContext.CurrentCustomer, request.Store, request.Vendor, _contextAccessor.WorkContext.WorkingLanguage.Id, request.Model.Email.Trim(), request.Model.FullName, subject, body, request.IpAddress, host: _contextAccessor.StoreContext.CurrentHost); diff --git a/src/Web/Grand.Web/Commands/Handler/Vendors/InsertVendorReviewCommandHandler.cs b/src/Web/Grand.Web/Commands/Handler/Vendors/InsertVendorReviewCommandHandler.cs index 09437083fc..c84e75968d 100644 --- a/src/Web/Grand.Web/Commands/Handler/Vendors/InsertVendorReviewCommandHandler.cs +++ b/src/Web/Grand.Web/Commands/Handler/Vendors/InsertVendorReviewCommandHandler.cs @@ -13,20 +13,20 @@ public class InsertVendorReviewCommandHandler : IRequestHandler Handle(InsertVendorReviewCommand request, Cancel //notify store owner if (_vendorSettings.NotifyVendorAboutNewVendorReviews) - await _messageProviderService.SendVendorReviewMessage(vendorReview, request.Store, + await _vendorMessageProviderService.SendVendorReviewMessage(vendorReview, request.Store, _languageSettings.DefaultAdminLanguageId, host: _contextAccessor.StoreContext.CurrentHost); return vendorReview; diff --git a/src/Web/Grand.Web/Controllers/AccountController.cs b/src/Web/Grand.Web/Controllers/AccountController.cs index cc7321e4ef..9061918f11 100644 --- a/src/Web/Grand.Web/Controllers/AccountController.cs +++ b/src/Web/Grand.Web/Controllers/AccountController.cs @@ -48,7 +48,7 @@ public AccountController( ICustomerManagerService customerManagerService, ICountryService countryService, IMediator mediator, - IMessageProviderService messageProviderService, + ICustomerMessageProviderService customerMessageProviderService, CaptchaSettings captchaSettings, CustomerSettings customerSettings, CustomerConfig customerConfig) @@ -61,7 +61,7 @@ public AccountController( _customerManagerService = customerManagerService; _customerSettings = customerSettings; _countryService = countryService; - _messageProviderService = messageProviderService; + _customerMessageProviderService = customerMessageProviderService; _captchaSettings = captchaSettings; _mediator = mediator; _customerConfig = customerConfig; @@ -173,7 +173,7 @@ public virtual async Task Courses() private readonly ICustomerManagerService _customerManagerService; private readonly ICountryService _countryService; private readonly IMediator _mediator; - private readonly IMessageProviderService _messageProviderService; + private readonly ICustomerMessageProviderService _customerMessageProviderService; private readonly CustomerSettings _customerSettings; private readonly CustomerConfig _customerConfig; private readonly CaptchaSettings _captchaSettings; @@ -629,7 +629,7 @@ await _mediator.Publish(new CustomerRegisteredEvent(_contextAccessor.WorkContext //email validation message await _customerService.UpdateUserField(_contextAccessor.WorkContext.CurrentCustomer, SystemCustomerFieldNames.AccountActivationToken, Guid.NewGuid().ToString()); - await _messageProviderService.SendCustomerEmailValidationMessage( + await _customerMessageProviderService.SendCustomerEmailValidationMessage( _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, _contextAccessor.WorkContext.WorkingLanguage.Id, host: _contextAccessor.StoreContext.CurrentHost); @@ -648,7 +648,7 @@ await _messageProviderService.SendCustomerEmailValidationMessage( case UserRegistrationType.Standard: { //send customer welcome message - await _messageProviderService.SendCustomerWelcomeMessage(_contextAccessor.WorkContext.CurrentCustomer, + await _customerMessageProviderService.SendCustomerWelcomeMessage(_contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, _contextAccessor.WorkContext.WorkingLanguage.Id, host: _contextAccessor.StoreContext.CurrentHost); @@ -796,7 +796,7 @@ public virtual async Task AccountActivation(string token, string await _customerService.UpdateUserField(customer, SystemCustomerFieldNames.AccountActivationToken, ""); //send welcome message - await _messageProviderService.SendCustomerWelcomeMessage(customer, _contextAccessor.StoreContext.CurrentStore, + await _customerMessageProviderService.SendCustomerWelcomeMessage(customer, _contextAccessor.StoreContext.CurrentStore, _contextAccessor.WorkContext.WorkingLanguage.Id, host: _contextAccessor.StoreContext.CurrentHost); var model = new AccountActivationModel { diff --git a/src/Web/Grand.Web/Controllers/KnowledgebaseController.cs b/src/Web/Grand.Web/Controllers/KnowledgebaseController.cs index 6a6a273816..be3256d843 100644 --- a/src/Web/Grand.Web/Controllers/KnowledgebaseController.cs +++ b/src/Web/Grand.Web/Controllers/KnowledgebaseController.cs @@ -35,7 +35,7 @@ public class KnowledgebaseController : BasePublicController private readonly IKnowledgebaseService _knowledgebaseService; private readonly KnowledgebaseSettings _knowledgebaseSettings; private readonly LanguageSettings _languageSettings; - private readonly IMessageProviderService _messageProviderService; + private readonly ICmsMessageProviderService _cmsMessageProviderService; private readonly IRequestPermissionService _permissionService; private readonly IRequestTranslationService _translationService; private readonly IContextAccessor _contextAccessor; @@ -47,7 +47,7 @@ public KnowledgebaseController( ICacheBase cacheBase, IAclService aclService, IRequestTranslationService translationService, - IMessageProviderService messageProviderService, + ICmsMessageProviderService cmsMessageProviderService, IDateTimeService dateTimeService, IRequestPermissionService permissionService, CustomerSettings customerSettings, @@ -62,7 +62,7 @@ public KnowledgebaseController( _translationService = translationService; _captchaSettings = captchaSettings; _languageSettings = languageSettings; - _messageProviderService = messageProviderService; + _cmsMessageProviderService = cmsMessageProviderService; _dateTimeService = dateTimeService; _customerSettings = customerSettings; _permissionService = permissionService; @@ -314,7 +314,7 @@ public virtual async Task ArticleCommentAdd(KnowledgebaseArticleM //notify a store owner if (_knowledgebaseSettings.NotifyAboutNewArticleComments) - await _messageProviderService.SendArticleCommentMessage(article, comment, + await _cmsMessageProviderService.SendArticleCommentMessage(article, comment, _languageSettings.DefaultAdminLanguageId, host: _contextAccessor.StoreContext.CurrentHost); //The text boxes should be cleared after a comment has been posted diff --git a/src/Web/Grand.Web/Controllers/VendorController.cs b/src/Web/Grand.Web/Controllers/VendorController.cs index aa47152ea1..9f11b9f4ff 100644 --- a/src/Web/Grand.Web/Controllers/VendorController.cs +++ b/src/Web/Grand.Web/Controllers/VendorController.cs @@ -33,7 +33,7 @@ public VendorController( IContextAccessor contextAccessor, IRequestTranslationService translationService, ICustomerService customerService, - IMessageProviderService messageProviderService, + IVendorMessageProviderService vendorMessageProviderService, IVendorService vendorService, ISlugService slugService, IPictureService pictureService, @@ -47,7 +47,7 @@ public VendorController( _contextAccessor = contextAccessor; _translationService = translationService; _customerService = customerService; - _messageProviderService = messageProviderService; + _vendorMessageProviderService = vendorMessageProviderService; _vendorService = vendorService; _slugService = slugService; _pictureService = pictureService; @@ -77,7 +77,7 @@ protected virtual async Task UpdatePictureSeoNames(Domain.Vendors.Vendor vendor) private readonly IContextAccessor _contextAccessor; private readonly IRequestTranslationService _translationService; private readonly ICustomerService _customerService; - private readonly IMessageProviderService _messageProviderService; + private readonly IVendorMessageProviderService _vendorMessageProviderService; private readonly IVendorService _vendorService; private readonly ISlugService _slugService; private readonly IPictureService _pictureService; @@ -167,7 +167,7 @@ await _customerService.UpdateCustomerField(_contextAccessor.WorkContext.CurrentC _contextAccessor.WorkContext.CurrentCustomer.VendorId); //notify store owner here (email) - await _messageProviderService.SendNewVendorAccountApplyStoreOwnerMessage(_contextAccessor.WorkContext.CurrentCustomer, + await _vendorMessageProviderService.SendNewVendorAccountApplyStoreOwnerMessage(_contextAccessor.WorkContext.CurrentCustomer, vendor, _contextAccessor.StoreContext.CurrentStore, _languageSettings.DefaultAdminLanguageId, host: _contextAccessor.StoreContext.CurrentHost); diff --git a/src/Web/Grand.Web/Controllers/WishlistController.cs b/src/Web/Grand.Web/Controllers/WishlistController.cs index 22dd2a7e29..5c788a17ce 100644 --- a/src/Web/Grand.Web/Controllers/WishlistController.cs +++ b/src/Web/Grand.Web/Controllers/WishlistController.cs @@ -211,7 +211,7 @@ public virtual async Task DeleteItemFromWishlist(string shoppingC [HttpPost] [DenySystemAccount] public virtual async Task EmailWishlist(WishlistEmailAFriendModel model, - [FromServices] IMessageProviderService messageProviderService, + [FromServices] ICatalogMessageProviderService catalogMessageProviderService, [FromServices] CaptchaSettings captchaSettings) { if (!await _permissionService.Authorize(StandardPermission.EnableWishlist) || @@ -225,7 +225,7 @@ public virtual async Task EmailWishlist(WishlistEmailAFriendModel if (ModelState.IsValid) { //email - await messageProviderService.SendWishlistEmailAFriendMessage(_contextAccessor.WorkContext.CurrentCustomer, + await catalogMessageProviderService.SendWishlistEmailAFriendMessage(_contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, _contextAccessor.WorkContext.WorkingLanguage.Id, model.YourEmailAddress, model.FriendEmail, FormatText.ConvertText(model.PersonalMessage), diff --git a/src/Web/Grand.Web/Features/Handlers/Customers/GetTwoFactorAuthenticationHandler.cs b/src/Web/Grand.Web/Features/Handlers/Customers/GetTwoFactorAuthenticationHandler.cs index 204c5751a0..8eee97de10 100644 --- a/src/Web/Grand.Web/Features/Handlers/Customers/GetTwoFactorAuthenticationHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Customers/GetTwoFactorAuthenticationHandler.cs @@ -11,16 +11,16 @@ public class GetTwoFactorAuthenticationHandler : IRequestHandler { private readonly CustomerSettings _customerSetting; - private readonly IMessageProviderService _messageProviderService; + private readonly ICustomerMessageProviderService _customerMessageProviderService; private readonly ITwoFactorAuthenticationService _twoFactorAuthenticationService; public GetTwoFactorAuthenticationHandler( ITwoFactorAuthenticationService twoFactorAuthenticationService, - IMessageProviderService messageProviderService, + ICustomerMessageProviderService customerMessageProviderService, CustomerSettings customerSetting) { _twoFactorAuthenticationService = twoFactorAuthenticationService; - _messageProviderService = messageProviderService; + _customerMessageProviderService = customerMessageProviderService; _customerSetting = customerSetting; } @@ -36,7 +36,7 @@ public GetTwoFactorAuthenticationHandler( TwoFactorAuthenticationType = _customerSetting.TwoFactorAuthenticationType }; if (_customerSetting.TwoFactorAuthenticationType == TwoFactorAuthenticationType.EmailVerification) - await _messageProviderService.SendCustomerEmailTokenValidationMessage(request.Customer, request.Store, + await _customerMessageProviderService.SendCustomerEmailTokenValidationMessage(request.Customer, request.Store, request.Customer.Id, host: request.Host); return model;