mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Enhance Store email capability (#3911)
* Enhance Store email capability
Currenty the new email rules can send an email when an invoice event occurs. However, there is currently no way to customize the email based on the invoice, making the feature a bit useless.
This PR:
* adds the rich text editor to the body input
* allows you to use some of the properties from the Invoice (based on greenfield api properties. I've taken a imple approach for now using just a string.replace mechanism, but we can update this to a dynamic linq approach so that users can customize further (e.g. `{Invoice.Metadata["something"].ToString().ToUpper()}`)
NOT READY:
Since this all takes place as a background service, there is an issue around how to handle items such as the "checkout link", as we are not aware of the btcpay url at that moment. Thoughts? @nicolasdorier
* fix typo and make it simpler for now
* remove dditor
This commit is contained in:
@@ -12,6 +12,7 @@ using BTCPayServer.Services;
|
|||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
@@ -389,7 +390,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime
|
ReceivedDate = paymentEntity.ReceivedTime.DateTime
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private InvoiceData ToModel(InvoiceEntity entity)
|
private InvoiceData ToModel(InvoiceEntity entity)
|
||||||
|
{
|
||||||
|
return ToModel(entity, _linkGenerator, Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InvoiceData ToModel(InvoiceEntity entity, LinkGenerator linkGenerator, HttpRequest? request)
|
||||||
{
|
{
|
||||||
var statuses = new List<InvoiceStatus>();
|
var statuses = new List<InvoiceStatus>();
|
||||||
var state = entity.GetInvoiceState();
|
var state = entity.GetInvoiceState();
|
||||||
@@ -411,7 +418,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
Amount = entity.Price,
|
Amount = entity.Price,
|
||||||
Type = entity.Type,
|
Type = entity.Type,
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
CheckoutLink = _linkGenerator.CheckoutLink(entity.Id, Request.Scheme, Request.Host, Request.PathBase),
|
CheckoutLink = request is null? null: linkGenerator.CheckoutLink(entity.Id, request.Scheme, request.Host, request.PathBase),
|
||||||
Status = entity.Status.ToModernStatus(),
|
Status = entity.Status.ToModernStatus(),
|
||||||
AdditionalStatus = entity.ExceptionStatus,
|
AdditionalStatus = entity.ExceptionStatus,
|
||||||
Currency = entity.Currency,
|
Currency = entity.Currency,
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Controllers.Greenfield;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Mails;
|
using BTCPayServer.Services.Mails;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MimeKit;
|
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||||
|
|
||||||
namespace BTCPayServer.HostedServices;
|
namespace BTCPayServer.HostedServices;
|
||||||
|
|
||||||
@@ -16,14 +20,19 @@ public class StoreEmailRuleProcessorSender : EventHostedServiceBase
|
|||||||
{
|
{
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
private readonly EmailSenderFactory _emailSenderFactory;
|
private readonly EmailSenderFactory _emailSenderFactory;
|
||||||
|
private readonly LinkGenerator _linkGenerator;
|
||||||
|
private readonly BTCPayServerEnvironment _environment;
|
||||||
|
|
||||||
public StoreEmailRuleProcessorSender(StoreRepository storeRepository, EventAggregator eventAggregator,
|
public StoreEmailRuleProcessorSender(StoreRepository storeRepository, EventAggregator eventAggregator,
|
||||||
ILogger<InvoiceEventSaverService> logger,
|
ILogger<InvoiceEventSaverService> logger,
|
||||||
EmailSenderFactory emailSenderFactory) : base(
|
EmailSenderFactory emailSenderFactory,
|
||||||
|
LinkGenerator linkGenerator, BTCPayServerEnvironment environment) : base(
|
||||||
eventAggregator, logger)
|
eventAggregator, logger)
|
||||||
{
|
{
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
_emailSenderFactory = emailSenderFactory;
|
_emailSenderFactory = emailSenderFactory;
|
||||||
|
_linkGenerator = linkGenerator;
|
||||||
|
_environment = environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SubscribeToEvents()
|
protected override void SubscribeToEvents()
|
||||||
@@ -54,17 +63,35 @@ public class StoreEmailRuleProcessorSender : EventHostedServiceBase
|
|||||||
foreach (UIStoresController.StoreEmailRule actionableRule in actionableRules)
|
foreach (UIStoresController.StoreEmailRule actionableRule in actionableRules)
|
||||||
{
|
{
|
||||||
var recipients = (actionableRule.To?.Split(",", StringSplitOptions.RemoveEmptyEntries)??Array.Empty<string>())
|
var recipients = (actionableRule.To?.Split(",", StringSplitOptions.RemoveEmptyEntries)??Array.Empty<string>())
|
||||||
.Select(o => { MailboxAddressValidator.TryParse(o, out var mb); return mb; })
|
.Select(o =>
|
||||||
|
{
|
||||||
|
MailboxAddressValidator.TryParse(o, out var mb);
|
||||||
|
return mb;
|
||||||
|
})
|
||||||
.Where(o => o != null)
|
.Where(o => o != null)
|
||||||
.ToList();
|
.ToList();
|
||||||
if (actionableRule.CustomerEmail && MailboxAddressValidator.TryParse(invoiceEvent.Invoice.Metadata.BuyerEmail, out var bmb))
|
if (actionableRule.CustomerEmail &&
|
||||||
|
MailboxAddressValidator.TryParse(invoiceEvent.Invoice.Metadata.BuyerEmail, out var bmb))
|
||||||
{
|
{
|
||||||
recipients.Add(bmb);
|
recipients.Add(bmb);
|
||||||
}
|
}
|
||||||
sender.SendEmail(recipients.ToArray(), null, null, actionableRule.Subject, actionableRule.Body);
|
var i = GreenfieldInvoiceController.ToModel(invoiceEvent.Invoice, _linkGenerator, null);
|
||||||
|
sender.SendEmail(recipients.ToArray(), null, null, Interpolator(actionableRule.Subject, i),
|
||||||
|
Interpolator(actionableRule.Body, i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string Interpolator(string str, InvoiceData i)
|
||||||
|
{
|
||||||
|
//TODO: we should switch to https://dotnetfiddle.net/MoqJFk later
|
||||||
|
return str.Replace("{Invoice.Id}", i.Id)
|
||||||
|
.Replace("{Invoice.StoreId}", i.StoreId)
|
||||||
|
.Replace("{Invoice.Price}", i.Amount.ToString(CultureInfo.InvariantCulture))
|
||||||
|
.Replace("{Invoice.Currency}", i.Currency)
|
||||||
|
.Replace("{Invoice.Status}", i.Status.ToString())
|
||||||
|
.Replace("{Invoice.AdditionalStatus}", i.AdditionalStatus.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,6 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@section PageFootContent {
|
@section PageFootContent {
|
||||||
<partial name="_ValidationScriptsPartial"/>
|
<partial name="_ValidationScriptsPartial"/>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// rich text editor
|
// rich text editor
|
||||||
if ($.summernote) {
|
if ($.summernote) {
|
||||||
$('.richtext').summernote({
|
$('.richtext').summernote({
|
||||||
|
|||||||
Reference in New Issue
Block a user