Skip to content

Commit

Permalink
Merge pull request #44 from QuantozTechnology/email-notifications
Browse files Browse the repository at this point in the history
Email notifications
  • Loading branch information
PJvGrol authored Dec 12, 2023
2 parents a4265a6 + 3145d51 commit 68c14df
Show file tree
Hide file tree
Showing 18 changed files with 517 additions and 4 deletions.
4 changes: 2 additions & 2 deletions backend/core/src/Core.API/Core.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="Quantoz.Nexus.Sdk.Shared" Version="1.0.1" />
<PackageReference Include="Quantoz.Nexus.Sdk.Token" Version="1.0.1" />
<PackageReference Include="Quantoz.Nexus.Sdk.Shared" Version="1.2.0" />
<PackageReference Include="Quantoz.Nexus.Sdk.Token" Version="1.2.0" />
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.6.0" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.6.0" />
<PackageReference Include="ReHackt.Extensions.Options.Validation" Version="7.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Core.Infrastructure.Compliance;
using Core.Infrastructure.Compliance.IPLocator;
using Core.Infrastructure.Compliance.Sanctionlist;
using Core.Infrastructure.Compliance.SendGridMailService;
using Core.Infrastructure.CustomerFileStorage;
using Core.Infrastructure.Jobs;
using Core.Infrastructure.Nexus;
Expand All @@ -31,6 +32,7 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi
.AddBlobStorage(configuration)
.AddCompliance(configuration)
.AddTOTPGenerator()
.AddSendGridMailService(configuration)
.AddBackgroundJobs(configuration);

return services;
Expand All @@ -51,6 +53,7 @@ private static IServiceCollection AddNexus(this IServiceCollection services, ICo
services.AddScoped<IAccountRepository, NexusAccountRepository>();
services.AddScoped<ITransactionRepository, NexusTransactionRepository>();
services.AddScoped<ISettingsRepository, NexusSettingsRepository>();
services.AddScoped<IMailsRepository, NexusMailsRepository>();

return services;
}
Expand Down Expand Up @@ -165,6 +168,20 @@ private static IServiceCollection AddTOTPGenerator(this IServiceCollection servi
return services;
}

private static IServiceCollection AddSendGridMailService(this IServiceCollection services, IConfiguration configuration)
{
services.AddOptions<SendGridMailServiceOptions>()
.Bind(configuration.GetSection("SendGridMailServiceOptions"))
.ValidateDataAnnotationsRecursively()
.ValidateOnStart();

services.AddSingleton(sp => sp.GetRequiredService<IOptions<SendGridMailServiceOptions>>().Value);

services.AddSingleton<ISendGridMailService, SendGridMailService>();

return services;
}

public static IServiceCollection AddBackgroundJobs(this IServiceCollection services, IConfiguration configuration)
{
services.AddQuartz(q =>
Expand All @@ -176,6 +193,9 @@ public static IServiceCollection AddBackgroundJobs(this IServiceCollection servi

// Register the ProcessPaymentRequestsJob, loading the schedule from configuration
q.AddJobAndTrigger<ProcessExpiredPaymentRequestJob>(configuration);

// Register the ProcessEmailsJob, loading the schedule from configuration
q.AddJobAndTrigger<ProcessEmailsJob>(configuration);
});

services.AddQuartzHostedService(opt =>
Expand Down
9 changes: 9 additions & 0 deletions backend/core/src/Core.API/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@
"BlobStorageOptions": {
"StorageConnectionString": "",
"ContainerName": ""
},
"SendGridMailServiceOptions": {
"ApiKey": "",
"Sender": "",
"Templates": {
"WithdrawalTemplateID": "",
"FundingtemplateID": ""
}
},
"ConnectionStrings": {
"Database": ""
Expand All @@ -69,6 +77,7 @@
"AllowedOrigins": []
},
"Quartz": {
"ProcessEmailsJob": "0 * * * * ?",
"ProcessCallbacksJob": "0/5 * * * * ?",
"ProcessExpiredPaymentRequestJob": "0/5 * * * * ?"
},
Expand Down
15 changes: 15 additions & 0 deletions backend/core/src/Core.Domain/Abstractions/ISendGridMailService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2023 Quantoz Technology B.V. and contributors. Licensed
// under the Apache License, Version 2.0. See the NOTICE file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

using Core.Domain.Entities.CustomerAggregate;
using Core.Domain.Entities.MailAggregate;
using Core.Domain.Entities.TransactionAggregate;

namespace Core.Domain.Abstractions
{
public interface ISendGridMailService
{
public Task SendMailAsync(Mail mail, Customer customer, Transaction transaction);
}
}
5 changes: 5 additions & 0 deletions backend/core/src/Core.Domain/DateTimeProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public static long ToUnixTimeInMilliseconds(DateTimeOffset dateTime)
{
return dateTime.ToUnixTimeMilliseconds();
}

public static string? FormatDateTimeWithoutMilliseconds(DateTimeOffset? dateTime)
{
return dateTime?.ToString("yyyy-MM-dd HH:mm:ss");
}
}

public class DateTimeProviderContext : IDisposable
Expand Down
54 changes: 54 additions & 0 deletions backend/core/src/Core.Domain/Entities/MailAggregate/Mail.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2023 Quantoz Technology B.V. and contributors. Licensed
// under the Apache License, Version 2.0. See the NOTICE file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

namespace Core.Domain.Entities.MailAggregate
{
public class Mail
{
public required string Code { get; set; }

public string? Created { get; set; }

public string? Sent { get; set; }

public string? Status { get; set; }

public string? Type { get; set; }

public int? Count { get; set; }

public MailEntityCodes? References { get; set; }

public MailContent? Content { get; set; }

public MailRecipient? Recipient { get; set; }
}

public class MailEntityCodes
{
public string? AccountCode { get; set; }

public string? CustomerCode { get; set; }

public string? TokenPaymentCode { get; set; }
}

public class MailContent
{
public string? Subject { get; set; }

public string? Html { get; set; }

public string? Text { get; set; }
}

public class MailRecipient
{
public string? Email { get; set; }

public string? CC { get; set; }

public string? BCC { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ public class Transaction

public required DateTimeOffset Created { get; set; }

public DateTimeOffset? Finished { get; set; }

public required string Status { get; set; }

public required string Type { get; set; }

public string? Memo { get; set; }

public required string Direction { get; set; }
public string? Direction { get; set; }

public Payment? Payment { get; set; }

Expand Down
15 changes: 15 additions & 0 deletions backend/core/src/Core.Domain/Repositories/IMailsRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2023 Quantoz Technology B.V. and contributors. Licensed
// under the Apache License, Version 2.0. See the NOTICE file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

using Core.Domain.Entities.MailAggregate;

namespace Core.Domain.Repositories
{
public interface IMailsRepository
{
Task<IEnumerable<Mail>> GetMailsAsync(string status, CancellationToken cancellationToken = default);

Task<Mail> UpdateMailSent(string code, CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public interface ITransactionRepository

public Task<Paged<Transaction>> GetAsync(string publicKey, int page, int pageSize, CancellationToken cancellationToken = default);

public Task<Paged<Transaction>> GetByCodeAsync(string code, CancellationToken cancellationToken = default);

public Task<WithdrawFees> GetWithdrawFeesAsync(Withdraw withdraw, CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2023 Quantoz Technology B.V. and contributors. Licensed
// under the Apache License, Version 2.0. See the NOTICE file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

using Newtonsoft.Json;

namespace Core.Infrastructure.Compliance.SendGridMailService
{
public class MailTemplate
{
[JsonProperty("customerFullName")]
public string? CustomerFullName { get; set; }

[JsonProperty("amount")]
public string? Amount { get; set; }

[JsonProperty("accountCode")]
public string? AccountCode { get; set; }

[JsonProperty("customerBankAccount")]
public string? BankAccount { get; set; }

[JsonProperty("transactionCode")]
public string? TransactionCode { get; set; }

[JsonProperty("payoutAmount")]
public string? PayoutAmount { get; set; }

[JsonProperty("createdDate")]
public string? CreatedDate { get; set; }

[JsonProperty("finishedDate")]
public string? FinishedDate { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2023 Quantoz Technology B.V. and contributors. Licensed
// under the Apache License, Version 2.0. See the NOTICE file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

using Core.Domain;
using Core.Domain.Abstractions;
using Core.Domain.Entities.CustomerAggregate;
using Core.Domain.Entities.MailAggregate;
using Core.Domain.Entities.TransactionAggregate;
using Core.Domain.Exceptions;
using Core.Infrastructure.Nexus;
using SendGrid;
using SendGrid.Helpers.Mail;
using System.Net;

namespace Core.Infrastructure.Compliance.SendGridMailService
{
public class SendGridMailService : ISendGridMailService
{
private readonly SendGridClient _sendGridClient;
private readonly SendGridMailServiceOptions _mailOptions;

public SendGridMailService(
SendGridMailServiceOptions mailOptions)
{
_mailOptions = mailOptions;
_sendGridClient = new SendGridClient(_mailOptions.ApiKey);
}

public async Task SendMailAsync(Mail mail, Customer customer, Transaction transaction)
{
if (mail == null)
{
throw new CustomErrorsException("MailService", "mail", "An error occured while sending mail.");
}

var from = new EmailAddress(_mailOptions.Sender);
var to = new EmailAddress(mail.Recipient?.Email) ?? throw new CustomErrorsException("MailService", "toAddress", "An error occured while sending mail.");

var msg = new SendGridMessage();

msg.SetFrom(new EmailAddress(from.Email, from.Name));
msg.AddTo(new EmailAddress(to.Email, to.Name));

// Payout
if (mail.Type == MailType.TransactionSellFinish.ToString())
{
msg.SetTemplateId(_mailOptions.Templates.WithdrawalTemplateID);
}
else if (mail.Type == MailType.TransactionBuyFinish.ToString())
{
msg.SetTemplateId(_mailOptions.Templates.FundingtemplateID);
}

// Fill in the dynamic template fields
var templateData = new MailTemplate()
{
CustomerFullName = customer?.GetName(),
AccountCode = mail.References?.AccountCode,
TransactionCode = mail.References?.TokenPaymentCode,
BankAccount = customer?.BankAccount,
Amount = transaction.Amount.ToString(),
CreatedDate = DateTimeProvider.FormatDateTimeWithoutMilliseconds(transaction.Created),
FinishedDate = DateTimeProvider.FormatDateTimeWithoutMilliseconds(transaction.Finished)
};

if (mail.Type == MailType.TransactionSellFinish.ToString())
{
//TODO: set payout amount when transaction details in nexus api would also return the net fiat amount
//templateData.PayoutAmount = transaction.NetFiatAmount.ToString()
}

msg.SetTemplateData(templateData);

var response = await _sendGridClient.SendEmailAsync(msg);

if (response.StatusCode != HttpStatusCode.Accepted)
{
throw new CustomErrorsException("MailService", "mail", "An error occured while sending mail.");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2023 Quantoz Technology B.V. and contributors. Licensed
// under the Apache License, Version 2.0. See the NOTICE file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

using System.ComponentModel.DataAnnotations;

namespace Core.Infrastructure.Compliance.SendGridMailService
{
public class SendGridMailServiceOptions
{
[Required]
public required string ApiKey { get; set; }

[Required]
public required Templates Templates { get; set; }

[Required]
public required string Sender { get; set; }
}

public class Templates
{
[Required]
public required string WithdrawalTemplateID { get; set; }

[Required]
public required string FundingtemplateID { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.1" />
<PackageReference Include="Otp.NET" Version="1.3.0" />
<PackageReference Include="Quantoz.Nexus.Sdk.Token" Version="1.0.1" />
<PackageReference Include="Quantoz.Nexus.Sdk.Token" Version="1.2.0" />
<PackageReference Include="Quartz" Version="3.6.0" />
<PackageReference Include="SendGrid" Version="9.28.1" />
<PackageReference Include="WindowsAzure.Storage" Version="9.3.3" />
</ItemGroup>

Expand Down
Loading

0 comments on commit 68c14df

Please sign in to comment.