mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Emails on store level
This commit is contained in:
@@ -28,7 +28,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly IEmailSender _emailSender;
|
||||
private readonly EmailSenderFactory _EmailSenderFactory;
|
||||
StoreRepository storeRepository;
|
||||
RoleManager<IdentityRole> _RoleManager;
|
||||
SettingsRepository _SettingsRepository;
|
||||
@@ -40,14 +40,14 @@ namespace BTCPayServer.Controllers
|
||||
RoleManager<IdentityRole> roleManager,
|
||||
StoreRepository storeRepository,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IEmailSender emailSender,
|
||||
EmailSenderFactory emailSenderFactory,
|
||||
SettingsRepository settingsRepository,
|
||||
Configuration.BTCPayServerOptions options)
|
||||
{
|
||||
this.storeRepository = storeRepository;
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_emailSender = emailSender;
|
||||
_EmailSenderFactory = emailSenderFactory;
|
||||
_RoleManager = roleManager;
|
||||
_SettingsRepository = settingsRepository;
|
||||
_Options = options;
|
||||
@@ -286,7 +286,8 @@ namespace BTCPayServer.Controllers
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
||||
RegisteredUserId = user.Id;
|
||||
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
|
||||
|
||||
_EmailSenderFactory.GetEmailSender().SendEmailConfirmation(model.Email, callbackUrl);
|
||||
if (!policies.RequiresConfirmedEmail)
|
||||
{
|
||||
if(logon)
|
||||
@@ -446,8 +447,9 @@ namespace BTCPayServer.Controllers
|
||||
// visit https://go.microsoft.com/fwlink/?LinkID=532713
|
||||
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
|
||||
await _emailSender.SendEmailAsync(model.Email, "Reset Password",
|
||||
_EmailSenderFactory.GetEmailSender().SendEmail(model.Email, "Reset Password",
|
||||
$"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
|
||||
|
||||
return RedirectToAction(nameof(ForgotPasswordConfirmation));
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly IEmailSender _emailSender;
|
||||
private readonly EmailSenderFactory _EmailSenderFactory;
|
||||
private readonly ILogger _logger;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
TokenRepository _TokenRepository;
|
||||
@@ -44,7 +44,7 @@ namespace BTCPayServer.Controllers
|
||||
public ManageController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IEmailSender emailSender,
|
||||
EmailSenderFactory emailSenderFactory,
|
||||
ILogger<ManageController> logger,
|
||||
UrlEncoder urlEncoder,
|
||||
TokenRepository tokenRepository,
|
||||
@@ -54,7 +54,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_emailSender = emailSender;
|
||||
_EmailSenderFactory = emailSenderFactory;
|
||||
_logger = logger;
|
||||
_urlEncoder = urlEncoder;
|
||||
_TokenRepository = tokenRepository;
|
||||
@@ -156,8 +156,7 @@ namespace BTCPayServer.Controllers
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
||||
var email = user.Email;
|
||||
await _emailSender.SendEmailConfirmationAsync(email, callbackUrl);
|
||||
|
||||
_EmailSenderFactory.GetEmailSender().SendEmailConfirmation(email, callbackUrl);
|
||||
StatusMessage = "Verification email sent. Please check your email.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
65
BTCPayServer/Controllers/StoresController.Email.cs
Normal file
65
BTCPayServer/Controllers/StoresController.Email.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.ServerViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments.Changelly;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class StoresController
|
||||
{
|
||||
|
||||
[Route("{storeId}/emails")]
|
||||
public IActionResult Emails()
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var data = store.GetStoreBlob().EmailSettings ?? new EmailSettings();
|
||||
return View(new EmailsViewModel() { Settings = data });
|
||||
}
|
||||
|
||||
[Route("{storeId}/emails")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Emails(string storeId, EmailsViewModel model, string command)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
if (command == "Test")
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!model.Settings.IsComplete())
|
||||
{
|
||||
model.StatusMessage = "Error: Required fields missing";
|
||||
return View(model);
|
||||
}
|
||||
var client = model.Settings.CreateSmtpClient();
|
||||
await client.SendMailAsync(model.Settings.From, model.TestEmail, "BTCPay test", "BTCPay test");
|
||||
model.StatusMessage = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
model.StatusMessage = "Error: " + ex.Message;
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
else // if(command == "Save")
|
||||
{
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
storeBlob.EmailSettings = model.Settings;
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = "Email settings modified";
|
||||
return RedirectToAction(nameof(UpdateStore), new {
|
||||
storeId});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ using BTCPayServer.Payments.Changelly;
|
||||
using BTCPayServer.Payments.CoinSwitch;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Mails;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@@ -403,6 +404,8 @@ namespace BTCPayServer.Data
|
||||
[Obsolete("Use SetWalletKeyPathRoot/GetWalletKeyPathRoot instead")]
|
||||
public Dictionary<string, string> WalletKeyPathRoots { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
public EmailSettings EmailSettings { get; set; }
|
||||
|
||||
public IPaymentFilter GetExcludedPaymentMethods()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace BTCPayServer.Services
|
||||
{
|
||||
public static class EmailSenderExtensions
|
||||
{
|
||||
public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link)
|
||||
public static void SendEmailConfirmation(this IEmailSender emailSender, string email, string link)
|
||||
{
|
||||
return emailSender.SendEmailAsync(email, "Confirm your email",
|
||||
emailSender.SendEmail(email, "Confirm your email",
|
||||
$"Please confirm your account by clicking this link: <a href='{HtmlEncoder.Default.Encode(link)}'>link</a>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,20 +46,21 @@ namespace BTCPayServer.HostedServices
|
||||
EventAggregator _EventAggregator;
|
||||
InvoiceRepository _InvoiceRepository;
|
||||
BTCPayNetworkProvider _NetworkProvider;
|
||||
IEmailSender _EmailSender;
|
||||
private readonly EmailSenderFactory _EmailSenderFactory;
|
||||
|
||||
public InvoiceNotificationManager(
|
||||
IBackgroundJobClient jobClient,
|
||||
EventAggregator eventAggregator,
|
||||
InvoiceRepository invoiceRepository,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
IEmailSender emailSender)
|
||||
ILogger<InvoiceNotificationManager> logger,
|
||||
EmailSenderFactory emailSenderFactory)
|
||||
{
|
||||
_JobClient = jobClient;
|
||||
_EventAggregator = eventAggregator;
|
||||
_InvoiceRepository = invoiceRepository;
|
||||
_NetworkProvider = networkProvider;
|
||||
_EmailSender = emailSender;
|
||||
_EmailSenderFactory = emailSenderFactory;
|
||||
}
|
||||
|
||||
void Notify(InvoiceEntity invoice, int? eventCode = null, string name = null)
|
||||
@@ -78,9 +79,12 @@ namespace BTCPayServer.HostedServices
|
||||
// TODO: Consider adding info on ItemDesc and payment info (amount)
|
||||
|
||||
var emailBody = NBitcoin.JsonConverters.Serializer.ToString(ipn);
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
_EmailSender.SendEmailAsync(invoice.NotificationEmail, $"BtcPayServer Invoice Notification - ${invoice.StoreId}", emailBody);
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
|
||||
_EmailSenderFactory.GetEmailSender(invoice.StoreId).SendEmail(
|
||||
invoice.NotificationEmail,
|
||||
$"BtcPayServer Invoice Notification - ${invoice.StoreId}",
|
||||
emailBody);
|
||||
|
||||
}
|
||||
if (string.IsNullOrEmpty(invoice.NotificationURL))
|
||||
return;
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace BTCPayServer.Hosting
|
||||
services.AddTransient<InvoiceController>();
|
||||
services.AddTransient<AppsPublicController>();
|
||||
// Add application services.
|
||||
services.AddTransient<IEmailSender, EmailSender>();
|
||||
services.AddSingleton<EmailSenderFactory>();
|
||||
// bundling
|
||||
|
||||
services.AddAuthorization(o => Policies.AddBTCPayPolicies(o));
|
||||
|
||||
@@ -1,47 +1,40 @@
|
||||
using BTCPayServer.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Mail;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Services.Mails
|
||||
{
|
||||
// This class is used by the application to send email for account confirmation and password reset.
|
||||
// For more details see https://go.microsoft.com/fwlink/?LinkID=532713
|
||||
public class EmailSender : IEmailSender
|
||||
public abstract class EmailSender : IEmailSender
|
||||
{
|
||||
IBackgroundJobClient _JobClient;
|
||||
SettingsRepository _Repository;
|
||||
public EmailSender(IBackgroundJobClient jobClient, SettingsRepository repository)
|
||||
|
||||
public EmailSender(IBackgroundJobClient jobClient)
|
||||
{
|
||||
if (jobClient == null)
|
||||
throw new ArgumentNullException(nameof(jobClient));
|
||||
_JobClient = jobClient;
|
||||
_Repository = repository;
|
||||
_JobClient = jobClient ?? throw new ArgumentNullException(nameof(jobClient));
|
||||
}
|
||||
public async Task SendEmailAsync(string email, string subject, string message)
|
||||
|
||||
public void SendEmail(string email, string subject, string message)
|
||||
{
|
||||
var settings = await _Repository.GetSettingAsync<EmailSettings>() ?? new EmailSettings();
|
||||
if (!settings.IsComplete())
|
||||
_JobClient.Schedule(async () =>
|
||||
{
|
||||
var emailSettings = await GetEmailSettings();
|
||||
if (emailSettings?.IsComplete() != true)
|
||||
{
|
||||
Logs.Configuration.LogWarning("Should have sent email, but email settings are not configured");
|
||||
return;
|
||||
}
|
||||
_JobClient.Schedule(() => SendMailCore(email, subject, message), TimeSpan.Zero);
|
||||
return;
|
||||
var smtp = emailSettings.CreateSmtpClient();
|
||||
var mail = new MailMessage(emailSettings.From, email, subject, message)
|
||||
{
|
||||
IsBodyHtml = true
|
||||
};
|
||||
await smtp.SendMailAsync(mail);
|
||||
|
||||
}, TimeSpan.Zero);
|
||||
}
|
||||
|
||||
public async Task SendMailCore(string email, string subject, string message)
|
||||
{
|
||||
var settings = await _Repository.GetSettingAsync<EmailSettings>() ?? new EmailSettings();
|
||||
if (!settings.IsComplete())
|
||||
throw new InvalidOperationException("Email settings not configured");
|
||||
var smtp = settings.CreateSmtpClient();
|
||||
MailMessage mail = new MailMessage(settings.From, email, subject, message);
|
||||
mail.IsBodyHtml = true;
|
||||
await smtp.SendMailAsync(mail);
|
||||
}
|
||||
public abstract Task<EmailSettings> GetEmailSettings();
|
||||
}
|
||||
}
|
||||
|
||||
31
BTCPayServer/Services/Mails/EmailSenderFactory.cs
Normal file
31
BTCPayServer/Services/Mails/EmailSenderFactory.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Stores;
|
||||
|
||||
namespace BTCPayServer.Services.Mails
|
||||
{
|
||||
public class EmailSenderFactory
|
||||
{
|
||||
private readonly IBackgroundJobClient _JobClient;
|
||||
private readonly SettingsRepository _Repository;
|
||||
private readonly StoreRepository _StoreRepository;
|
||||
|
||||
public EmailSenderFactory(IBackgroundJobClient jobClient,
|
||||
SettingsRepository repository,
|
||||
StoreRepository storeRepository)
|
||||
{
|
||||
_JobClient = jobClient;
|
||||
_Repository = repository;
|
||||
_StoreRepository = storeRepository;
|
||||
}
|
||||
|
||||
public IEmailSender GetEmailSender(string storeId = null)
|
||||
{
|
||||
var serverSender = new ServerEmailSender(_Repository, _JobClient);
|
||||
if (string.IsNullOrEmpty(storeId))
|
||||
return serverSender;
|
||||
return new StoreEmailSender(_StoreRepository, serverSender, _JobClient, storeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,6 @@ namespace BTCPayServer.Services.Mails
|
||||
{
|
||||
public interface IEmailSender
|
||||
{
|
||||
Task SendEmailAsync(string email, string subject, string message);
|
||||
void SendEmail(string email, string subject, string message);
|
||||
}
|
||||
}
|
||||
|
||||
25
BTCPayServer/Services/Mails/ServerEmailSender.cs
Normal file
25
BTCPayServer/Services/Mails/ServerEmailSender.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Services.Mails
|
||||
{
|
||||
class ServerEmailSender : EmailSender
|
||||
{
|
||||
public ServerEmailSender(SettingsRepository settingsRepository,
|
||||
IBackgroundJobClient backgroundJobClient) : base(backgroundJobClient)
|
||||
{
|
||||
if (settingsRepository == null)
|
||||
throw new ArgumentNullException(nameof(settingsRepository));
|
||||
SettingsRepository = settingsRepository;
|
||||
}
|
||||
|
||||
public SettingsRepository SettingsRepository { get; }
|
||||
|
||||
public override Task<EmailSettings> GetEmailSettings()
|
||||
{
|
||||
return SettingsRepository.GetSettingAsync<EmailSettings>();
|
||||
}
|
||||
}
|
||||
}
|
||||
38
BTCPayServer/Services/Mails/StoreEmailSender.cs
Normal file
38
BTCPayServer/Services/Mails/StoreEmailSender.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services.Stores;
|
||||
|
||||
namespace BTCPayServer.Services.Mails
|
||||
{
|
||||
class StoreEmailSender : EmailSender
|
||||
{
|
||||
public StoreEmailSender(StoreRepository storeRepository,
|
||||
EmailSender fallback,
|
||||
IBackgroundJobClient backgroundJobClient,
|
||||
string storeId) : base(backgroundJobClient)
|
||||
{
|
||||
if (storeId == null)
|
||||
throw new ArgumentNullException(nameof(storeId));
|
||||
StoreRepository = storeRepository;
|
||||
FallbackSender = fallback;
|
||||
StoreId = storeId;
|
||||
}
|
||||
|
||||
public StoreRepository StoreRepository { get; }
|
||||
public EmailSender FallbackSender { get; }
|
||||
public string StoreId { get; }
|
||||
|
||||
public override async Task<EmailSettings> GetEmailSettings()
|
||||
{
|
||||
var store = await StoreRepository.FindStore(StoreId);
|
||||
var emailSettings = store.GetStoreBlob().EmailSettings;
|
||||
if (emailSettings?.IsComplete() == true)
|
||||
{
|
||||
return emailSettings;
|
||||
}
|
||||
return await FallbackSender.GetEmailSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
68
BTCPayServer/Views/Stores/Emails.cshtml
Normal file
68
BTCPayServer/Views/Stores/Emails.cshtml
Normal file
@@ -0,0 +1,68 @@
|
||||
@model BTCPayServer.Models.ServerViewModels.EmailsViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData.SetActivePageAndTitle(StoreNavPages.Index, "Update Store Email Settings");
|
||||
}
|
||||
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="StatusMessage" />
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="Settings.Server"></label>
|
||||
<input asp-for="Settings.Server" class="form-control" />
|
||||
<span asp-validation-for="Settings.Server" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Settings.Port"></label>
|
||||
<input asp-for="Settings.Port" class="form-control" />
|
||||
<span asp-validation-for="Settings.Port" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Settings.From"></label>
|
||||
<input asp-for="Settings.From" class="form-control" />
|
||||
<span asp-validation-for="Settings.From" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Settings.Login"></label>
|
||||
<input asp-for="Settings.Login" class="form-control" />
|
||||
<span asp-validation-for="Settings.Login" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Settings.Password"></label>
|
||||
<input asp-for="Settings.Password" type="password" class="form-control" />
|
||||
<span asp-validation-for="Settings.Password" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Settings.EnableSSL"></label>
|
||||
<input asp-for="Settings.EnableSSL" type="checkbox" class="form-check-inline" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="TestEmail"></label>
|
||||
<input asp-for="TestEmail" class="form-control" />
|
||||
<span asp-validation-for="TestEmail" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Test">Test</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
@@ -214,7 +214,29 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<h5>Services</h5>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Service</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Email
|
||||
</td>
|
||||
<td style="text-align:right"><a asp-action="Emails" >Modify</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@if(Model.CanDelete)
|
||||
{
|
||||
<div class="form-group">
|
||||
|
||||
Reference in New Issue
Block a user