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