diff --git a/backend/core/src/Core.API/Core.API.csproj b/backend/core/src/Core.API/Core.API.csproj
index f4c55484..54635bdf 100644
--- a/backend/core/src/Core.API/Core.API.csproj
+++ b/backend/core/src/Core.API/Core.API.csproj
@@ -23,8 +23,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/backend/core/src/Core.API/DependencyInjection/InfrastructureInjection.cs b/backend/core/src/Core.API/DependencyInjection/InfrastructureInjection.cs
index 375c8ccb..636cb3f6 100644
--- a/backend/core/src/Core.API/DependencyInjection/InfrastructureInjection.cs
+++ b/backend/core/src/Core.API/DependencyInjection/InfrastructureInjection.cs
@@ -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;
@@ -31,6 +32,7 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi
.AddBlobStorage(configuration)
.AddCompliance(configuration)
.AddTOTPGenerator()
+ .AddSendGridMailService(configuration)
.AddBackgroundJobs(configuration);
return services;
@@ -51,6 +53,7 @@ private static IServiceCollection AddNexus(this IServiceCollection services, ICo
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
return services;
}
@@ -165,6 +168,20 @@ private static IServiceCollection AddTOTPGenerator(this IServiceCollection servi
return services;
}
+ private static IServiceCollection AddSendGridMailService(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddOptions()
+ .Bind(configuration.GetSection("SendGridMailServiceOptions"))
+ .ValidateDataAnnotationsRecursively()
+ .ValidateOnStart();
+
+ services.AddSingleton(sp => sp.GetRequiredService>().Value);
+
+ services.AddSingleton();
+
+ return services;
+ }
+
public static IServiceCollection AddBackgroundJobs(this IServiceCollection services, IConfiguration configuration)
{
services.AddQuartz(q =>
@@ -176,6 +193,9 @@ public static IServiceCollection AddBackgroundJobs(this IServiceCollection servi
// Register the ProcessPaymentRequestsJob, loading the schedule from configuration
q.AddJobAndTrigger(configuration);
+
+ // Register the ProcessEmailsJob, loading the schedule from configuration
+ q.AddJobAndTrigger(configuration);
});
services.AddQuartzHostedService(opt =>
diff --git a/backend/core/src/Core.API/appsettings.json b/backend/core/src/Core.API/appsettings.json
index 48136497..dfb294eb 100644
--- a/backend/core/src/Core.API/appsettings.json
+++ b/backend/core/src/Core.API/appsettings.json
@@ -59,6 +59,14 @@
"BlobStorageOptions": {
"StorageConnectionString": "",
"ContainerName": ""
+ },
+ "SendGridMailServiceOptions": {
+ "ApiKey": "",
+ "Sender": "",
+ "Templates": {
+ "WithdrawalTemplateID": "",
+ "FundingtemplateID": ""
+ }
},
"ConnectionStrings": {
"Database": ""
@@ -69,6 +77,7 @@
"AllowedOrigins": []
},
"Quartz": {
+ "ProcessEmailsJob": "0 * * * * ?",
"ProcessCallbacksJob": "0/5 * * * * ?",
"ProcessExpiredPaymentRequestJob": "0/5 * * * * ?"
},
diff --git a/backend/core/src/Core.Domain/Abstractions/ISendGridMailService.cs b/backend/core/src/Core.Domain/Abstractions/ISendGridMailService.cs
new file mode 100644
index 00000000..13a4cfdc
--- /dev/null
+++ b/backend/core/src/Core.Domain/Abstractions/ISendGridMailService.cs
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/backend/core/src/Core.Domain/DateTimeProvider.cs b/backend/core/src/Core.Domain/DateTimeProvider.cs
index b57368ba..365635d1 100644
--- a/backend/core/src/Core.Domain/DateTimeProvider.cs
+++ b/backend/core/src/Core.Domain/DateTimeProvider.cs
@@ -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
diff --git a/backend/core/src/Core.Domain/Entities/MailAggregate/Mail.cs b/backend/core/src/Core.Domain/Entities/MailAggregate/Mail.cs
new file mode 100644
index 00000000..b43a1423
--- /dev/null
+++ b/backend/core/src/Core.Domain/Entities/MailAggregate/Mail.cs
@@ -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; }
+ }
+}
diff --git a/backend/core/src/Core.Domain/Entities/TransactionAggregate/Transaction.cs b/backend/core/src/Core.Domain/Entities/TransactionAggregate/Transaction.cs
index 371cb962..1e69b1e1 100644
--- a/backend/core/src/Core.Domain/Entities/TransactionAggregate/Transaction.cs
+++ b/backend/core/src/Core.Domain/Entities/TransactionAggregate/Transaction.cs
@@ -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; }
diff --git a/backend/core/src/Core.Domain/Repositories/IMailsRepository.cs b/backend/core/src/Core.Domain/Repositories/IMailsRepository.cs
new file mode 100644
index 00000000..e1a57690
--- /dev/null
+++ b/backend/core/src/Core.Domain/Repositories/IMailsRepository.cs
@@ -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> GetMailsAsync(string status, CancellationToken cancellationToken = default);
+
+ Task UpdateMailSent(string code, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/backend/core/src/Core.Domain/Repositories/ITransactionRepository.cs b/backend/core/src/Core.Domain/Repositories/ITransactionRepository.cs
index 5c8ae1d9..63a4f9b3 100644
--- a/backend/core/src/Core.Domain/Repositories/ITransactionRepository.cs
+++ b/backend/core/src/Core.Domain/Repositories/ITransactionRepository.cs
@@ -15,6 +15,8 @@ public interface ITransactionRepository
public Task> GetAsync(string publicKey, int page, int pageSize, CancellationToken cancellationToken = default);
+ public Task> GetByCodeAsync(string code, CancellationToken cancellationToken = default);
+
public Task GetWithdrawFeesAsync(Withdraw withdraw, CancellationToken cancellationToken = default);
}
}
diff --git a/backend/core/src/Core.Infrastructure/Compliance/SendGridMailService/MailTemplate.cs b/backend/core/src/Core.Infrastructure/Compliance/SendGridMailService/MailTemplate.cs
new file mode 100644
index 00000000..3b831014
--- /dev/null
+++ b/backend/core/src/Core.Infrastructure/Compliance/SendGridMailService/MailTemplate.cs
@@ -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; }
+ }
+}
diff --git a/backend/core/src/Core.Infrastructure/Compliance/SendGridMailService/SendGridMailService.cs b/backend/core/src/Core.Infrastructure/Compliance/SendGridMailService/SendGridMailService.cs
new file mode 100644
index 00000000..525bc463
--- /dev/null
+++ b/backend/core/src/Core.Infrastructure/Compliance/SendGridMailService/SendGridMailService.cs
@@ -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.");
+ }
+ }
+ }
+}
diff --git a/backend/core/src/Core.Infrastructure/Compliance/SendGridMailService/SendGridMailServiceOptions.cs b/backend/core/src/Core.Infrastructure/Compliance/SendGridMailService/SendGridMailServiceOptions.cs
new file mode 100644
index 00000000..6e245732
--- /dev/null
+++ b/backend/core/src/Core.Infrastructure/Compliance/SendGridMailService/SendGridMailServiceOptions.cs
@@ -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; }
+ }
+}
diff --git a/backend/core/src/Core.Infrastructure/Core.Infrastructure.csproj b/backend/core/src/Core.Infrastructure/Core.Infrastructure.csproj
index 802e0a6f..844c30f4 100644
--- a/backend/core/src/Core.Infrastructure/Core.Infrastructure.csproj
+++ b/backend/core/src/Core.Infrastructure/Core.Infrastructure.csproj
@@ -9,8 +9,9 @@
-
+
+
diff --git a/backend/core/src/Core.Infrastructure/Jobs/ProcessEmailsJob.cs b/backend/core/src/Core.Infrastructure/Jobs/ProcessEmailsJob.cs
new file mode 100644
index 00000000..a906d2ae
--- /dev/null
+++ b/backend/core/src/Core.Infrastructure/Jobs/ProcessEmailsJob.cs
@@ -0,0 +1,88 @@
+// 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.Abstractions;
+using Core.Domain.Entities.TransactionAggregate;
+using Core.Domain.Exceptions;
+using Core.Domain.Repositories;
+using Core.Infrastructure.Nexus;
+using Microsoft.Extensions.Logging;
+using Quartz;
+
+namespace Core.Infrastructure.Jobs
+{
+ public class ProcessEmailsJob : IJob
+ {
+ private readonly ILogger _logger;
+ private readonly IMailsRepository _mailsRepository;
+ private readonly ICustomerRepository _customerRepository;
+ private readonly ITransactionRepository _transactionRepository;
+ private readonly IUnitOfWork _unitOfWork;
+ private readonly ISendGridMailService _sendGridMailService;
+
+ public ProcessEmailsJob(
+ ILogger logger,
+ IMailsRepository mailsRepository,
+ ICustomerRepository customerRepository,
+ ITransactionRepository transactionRepository,
+ ISendGridMailService sendGridMailService,
+ IUnitOfWork unitOfWork)
+ {
+ _logger = logger;
+ _mailsRepository = mailsRepository;
+ _customerRepository = customerRepository;
+ _transactionRepository = transactionRepository;
+ _sendGridMailService = sendGridMailService;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task Execute(IJobExecutionContext context)
+ {
+ var mails = _mailsRepository.GetMailsAsync(MailStatus.ReadyToSend.ToString(), context.CancellationToken).Result;
+
+ if (mails != null && mails.Any())
+ {
+ foreach (var mail in mails)
+ {
+ var customerCode = mail.References?.CustomerCode;
+
+ if (string.IsNullOrWhiteSpace(customerCode))
+ {
+ throw new CustomErrorsException("MailService", "customerCode", "An error occured while sending mail.");
+ }
+
+ var customer = await _customerRepository.GetAsync(customerCode, context.CancellationToken);
+
+ if (mail.References == null || string.IsNullOrWhiteSpace(mail.References.TokenPaymentCode))
+ {
+ throw new CustomErrorsException("MailService", "TokenPaymentCode", "An error occured while sending mail.");
+ }
+
+ var transactions = await _transactionRepository.GetByCodeAsync(mail.References.TokenPaymentCode, context.CancellationToken);
+
+ Transaction? transaction = null;
+ if (transactions != null && transactions.Items.Any())
+ {
+ transaction = transactions.Items.FirstOrDefault();
+ }
+
+ try
+ {
+ await _sendGridMailService.SendMailAsync(mail, customer, transaction!);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("An error occured sending email {code} with message {message}", mail.Code, ex.Message);
+ }
+
+ // once email has been sent, call nexus to update the status of this mail to 'Sent'
+ await _mailsRepository.UpdateMailSent(mail.Code);
+ }
+ }
+
+ await _unitOfWork.SaveChangesAsync();
+ }
+ }
+}
+
diff --git a/backend/core/src/Core.Infrastructure/Nexus/Enums.cs b/backend/core/src/Core.Infrastructure/Nexus/Enums.cs
index 463e2690..0787ebf8 100644
--- a/backend/core/src/Core.Infrastructure/Nexus/Enums.cs
+++ b/backend/core/src/Core.Infrastructure/Nexus/Enums.cs
@@ -26,4 +26,15 @@ public enum NexusErrorCodes
InvalidProperty = 5,
TransactionNotFoundError = 6
}
+
+ public enum MailStatus
+ {
+ ReadyToSend = 1
+ }
+
+ public enum MailType
+ {
+ TransactionSellFinish = 1,
+ TransactionBuyFinish = 2
+ }
}
diff --git a/backend/core/src/Core.Infrastructure/Nexus/Repositories/NexusMailsRepository.cs b/backend/core/src/Core.Infrastructure/Nexus/Repositories/NexusMailsRepository.cs
new file mode 100644
index 00000000..f81ccea5
--- /dev/null
+++ b/backend/core/src/Core.Infrastructure/Nexus/Repositories/NexusMailsRepository.cs
@@ -0,0 +1,85 @@
+// 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;
+using Core.Domain.Repositories;
+using Nexus.Sdk.Shared.Responses;
+using Nexus.Sdk.Token;
+
+namespace Core.Infrastructure.Nexus.Repositories
+{
+ public class NexusMailsRepository : IMailsRepository
+ {
+ private readonly ITokenServer _tokenServer;
+
+ public NexusMailsRepository(ITokenServer tokenServer)
+ {
+ _tokenServer = tokenServer;
+ }
+
+ public async Task> GetMailsAsync(string status, CancellationToken cancellationToken = default)
+ {
+ var query = new Dictionary()
+ {
+ {
+ "status", status
+ }
+ };
+
+ var response = await _tokenServer.Compliance.Mails.Get(query);
+
+ var mails = response.Records;
+
+ var items = new List();
+
+ foreach (var mail in mails)
+ {
+ var item = ConvertToMailAsync(mail, cancellationToken);
+ items.Add(item);
+ }
+
+ return items;
+ }
+
+ public async Task UpdateMailSent(string code, CancellationToken cancellationToken = default)
+ {
+ var response = await _tokenServer.Compliance.Mails.UpdateMailSent(code);
+
+ return ConvertToMailAsync(response, cancellationToken);
+ }
+
+ private static Mail ConvertToMailAsync(MailsResponse mail, CancellationToken cancellationToken = default)
+ {
+ var response = new Mail
+ {
+ Code = mail.Code,
+ Type = mail.Type,
+ Status = mail.Status,
+ Content = new Domain.Entities.MailAggregate.MailContent
+ {
+ Html = mail.Content?.Html,
+ Subject = mail.Content?.Subject,
+ Text = mail.Content?.Text
+ },
+ Count = mail.Count,
+ Created = mail.Created,
+ Recipient = new Domain.Entities.MailAggregate.MailRecipient
+ {
+ BCC = mail.Recipient?.BCC,
+ CC = mail.Recipient?.CC,
+ Email = mail.Recipient!.Email
+ },
+ References = new Domain.Entities.MailAggregate.MailEntityCodes
+ {
+ AccountCode = mail.References?.AccountCode,
+ CustomerCode = mail.References?.CustomerCode,
+ TokenPaymentCode = mail.References?.TokenPaymentCode
+ },
+ Sent = mail.Sent
+ };
+
+ return response;
+ }
+ }
+}
diff --git a/backend/core/src/Core.Infrastructure/Nexus/Repositories/NexusTransactionRepository.cs b/backend/core/src/Core.Infrastructure/Nexus/Repositories/NexusTransactionRepository.cs
index c594a243..0f3aa7b7 100644
--- a/backend/core/src/Core.Infrastructure/Nexus/Repositories/NexusTransactionRepository.cs
+++ b/backend/core/src/Core.Infrastructure/Nexus/Repositories/NexusTransactionRepository.cs
@@ -109,6 +109,32 @@ public async Task> GetAsync(string publicKey, int page, int p
};
}
+ public async Task> GetByCodeAsync(string code, CancellationToken cancellationToken = default)
+ {
+ int page = 1; // default value
+ int pageSize = 10; // default value
+
+ var response = await _tokenServer.Operations.Get(code);
+
+ var operations = response.Records;
+
+ var items = new List();
+
+ foreach (var operation in operations)
+ {
+ var item = await ConvertAsync(operation, cancellationToken);
+ items.Add(item);
+ }
+
+ return new Paged
+ {
+ Items = items,
+ Page = page,
+ PageSize = pageSize,
+ Total = response.Total
+ };
+ }
+
#region private methods
private async Task CreateAlgorandPayment(Payment payment, string? ip = null)
@@ -249,6 +275,36 @@ private async Task ConvertToTransactionAsync(string publicKey, Toke
return transaction;
}
+
+ private async Task ConvertAsync(TokenOperationResponse operation, CancellationToken cancellationToken = default)
+ {
+ var transaction = new Transaction
+ {
+ Amount = operation.Amount,
+ FromAccountCode = operation.SenderAccount?.AccountCode,
+ ToAccountCode = operation.ReceiverAccount?.AccountCode,
+ Created = DateTimeOffset.Parse(operation.Created),
+ Finished = operation.Finished != null ? DateTimeOffset.Parse(operation.Finished) : null,
+ Status = operation.Status,
+ TokenCode = operation.TokenCode,
+ TransactionCode = operation.Code,
+ Memo = operation.Memo,
+ Type = operation.Type
+ };
+
+ if (transaction.Type == "Payment")
+ {
+ var hasTransaction = await _paymentRepository.HasTransactionAsync(transaction.TransactionCode, cancellationToken);
+
+ if (hasTransaction)
+ {
+ transaction.Payment = await _paymentRepository.GetByTransactionCodeAsync(transaction.TransactionCode, cancellationToken);
+ }
+ }
+
+ return transaction;
+ }
+
#endregion
}
}
diff --git a/backend/core/src/Core.Presentation/Models/Responses/TransactionResponse.cs b/backend/core/src/Core.Presentation/Models/Responses/TransactionResponse.cs
index cea3cadd..de8cd813 100644
--- a/backend/core/src/Core.Presentation/Models/Responses/TransactionResponse.cs
+++ b/backend/core/src/Core.Presentation/Models/Responses/TransactionResponse.cs
@@ -25,6 +25,8 @@ public class TransactionResponse
public long Created { get; set; }
+ public long? Finished { get; set; }
+
public required string Status { get; set; }
public required string Type { get; set; }
@@ -41,6 +43,7 @@ public static TransactionResponse FromTransaction(Transaction transaction)
ToAccountCode = transaction.ToAccountCode,
Amount = transaction.Amount,
Created = DateTimeProvider.ToUnixTimeInMilliseconds(transaction.Created),
+ Finished = DateTimeProvider.ToUnixTimeInMilliseconds(transaction.Finished),
Direction = transaction.Direction,
Status = transaction.Status,
TokenCode = transaction.TokenCode,