Skip to content
Merged

Dev #2145

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
using System;
using System.Threading.Tasks;
using Unity.Modules.Shared.MessageBrokers.RabbitMQ.Interfaces;
using Unity.Payments.RabbitMQ.QueueMessages;
using System;
using Volo.Abp.MultiTenancy;
using Unity.Payments.Integrations.Cas;
using Unity.Payments.RabbitMQ.QueueMessages;

namespace Unity.Payments.Integrations.RabbitMQ;

public class InvoiceConsumer(InvoiceService invoiceService,
ICurrentTenant currentTenant) : IQueueConsumer<InvoiceMessages>
/// <summary>
/// Processes invoice creation messages from RabbitMQ.
/// Tenant context and audit scope are established by <see cref="QueueConsumerHandler{TMessageConsumer,TQueueMessage}"/>
/// before this consumer is invoked — no manual wiring needed here.
/// </summary>
public class InvoiceConsumer(
InvoiceService invoiceService
) : IQueueConsumer<InvoiceMessages>
{
public async Task ConsumeAsync(InvoiceMessages invoiceMessage)
{
if (invoiceMessage != null && !invoiceMessage.InvoiceNumber.IsNullOrEmpty() && invoiceMessage.TenantId != Guid.Empty)
if (invoiceMessage == null ||
invoiceMessage.InvoiceNumber.IsNullOrEmpty() ||
invoiceMessage.TenantId == Guid.Empty)
{
using (currentTenant.Change(invoiceMessage.TenantId))
{
await invoiceService.CreateInvoiceByPaymentRequestAsync(invoiceMessage.InvoiceNumber);
}
return;
}

await invoiceService.CreateInvoiceByPaymentRequestAsync(invoiceMessage.InvoiceNumber);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Unity.Payments.RabbitMQ.QueueMessages
{
public class InvoiceMessages : IQueueMessage
public class InvoiceMessages : ITenantedQueueMessage
{
public Guid MessageId { get; set; }
public TimeSpan TimeToLive { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Unity.Payments.RabbitMQ.QueueMessages
{
public class ReconcilePaymentMessages : IQueueMessage
public class ReconcilePaymentMessages : ITenantedQueueMessage
{
public Guid MessageId { get; set; }
public TimeSpan TimeToLive { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
using System;
using System.Threading.Tasks;
using Unity.Modules.Shared.MessageBrokers.RabbitMQ.Interfaces;
using Unity.Payments.RabbitMQ.QueueMessages;
using System;
using Unity.Payments.PaymentRequests;
using Unity.Payments.Integrations.Cas;
using Volo.Abp.MultiTenancy;
using Unity.Payments.PaymentRequests;
using Unity.Payments.RabbitMQ.QueueMessages;

namespace Unity.Payments.Integrations.RabbitMQ;

/// <summary>
/// Processes payment reconciliation messages from RabbitMQ.
/// Tenant context and audit scope are established by <see cref="QueueConsumerHandler{TMessageConsumer,TQueueMessage}"/>
/// before this consumer is invoked — no manual wiring needed here.
/// </summary>
public class ReconciliationConsumer(
CasPaymentRequestCoordinator casPaymentRequestCoordinator,
InvoiceService invoiceService,
ICurrentTenant currentTenant
) : IQueueConsumer<ReconcilePaymentMessages>
CasPaymentRequestCoordinator casPaymentRequestCoordinator,
InvoiceService invoiceService
) : IQueueConsumer<ReconcilePaymentMessages>
{
public async Task ConsumeAsync(ReconcilePaymentMessages reconcilePaymentMessage)
{
if (reconcilePaymentMessage != null && !reconcilePaymentMessage.InvoiceNumber.IsNullOrEmpty() && reconcilePaymentMessage.TenantId != Guid.Empty)
{
if (reconcilePaymentMessage == null ||
reconcilePaymentMessage.InvoiceNumber.IsNullOrEmpty() ||
reconcilePaymentMessage.TenantId == Guid.Empty)
{
return;
}

using (currentTenant.Change(reconcilePaymentMessage.TenantId))
{
// string invoiceNumber, string supplierNumber, string siteNumber)
// Go to CAS retrieve the status of the payment
CasPaymentSearchResult result = await invoiceService.GetCasPaymentAsync(
reconcilePaymentMessage.TenantId,
reconcilePaymentMessage.InvoiceNumber,
reconcilePaymentMessage.SupplierNumber,
reconcilePaymentMessage.SiteNumber);
// string invoiceNumber, string supplierNumber, string siteNumber)
// Go to CAS retrieve the status of the payment
CasPaymentSearchResult result = await invoiceService.GetCasPaymentAsync(
reconcilePaymentMessage.TenantId,
reconcilePaymentMessage.InvoiceNumber,
reconcilePaymentMessage.SupplierNumber,
reconcilePaymentMessage.SiteNumber);

if (result != null && result.InvoiceStatus != null && result.InvoiceStatus != "")
{
await casPaymentRequestCoordinator.UpdatePaymentRequestStatus(reconcilePaymentMessage.TenantId, reconcilePaymentMessage.PaymentRequestId, result);
}
}

if (!string.IsNullOrEmpty(result?.InvoiceStatus))
{
await casPaymentRequestCoordinator.UpdatePaymentRequestStatus(
reconcilePaymentMessage.TenantId,
reconcilePaymentMessage.PaymentRequestId,
result);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,15 @@

namespace Unity.Payments.PaymentRequests
{
public class CasPaymentRequestCoordinator : ApplicationService
{
private readonly IPaymentRequestRepository _paymentRequestsRepository;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly ITenantRepository _tenantRepository;
private readonly ICurrentTenant _currentTenant;
private readonly PaymentQueueService _paymentQueueService;
private static int TenMinutes = 10;

public CasPaymentRequestCoordinator(
PaymentQueueService paymentQueueService,
public class CasPaymentRequestCoordinator(PaymentQueueService paymentQueueService,
IPaymentRequestRepository paymentRequestsRepository,
IUnitOfWorkManager unitOfWorkManager,
ITenantRepository tenantRepository,
ICurrentTenant currentTenant)
{
_paymentQueueService = paymentQueueService;
_paymentRequestsRepository = paymentRequestsRepository;
_tenantRepository = tenantRepository;
_currentTenant = currentTenant;
_unitOfWorkManager = unitOfWorkManager;
}
ICurrentTenant currentTenant) : ApplicationService
{

private static int TenMinutes = 10;


protected virtual dynamic GetPaymentRequestObject(
Guid paymentRequestId,
Expand All @@ -60,7 +47,7 @@ public async Task AddPaymentRequestsToInvoiceQueue(PaymentRequest paymentRequest
{
try
{
if (!string.IsNullOrEmpty(paymentRequest.InvoiceNumber) && _currentTenant != null && _currentTenant.Id != null)
if (!string.IsNullOrEmpty(paymentRequest.InvoiceNumber) && currentTenant != null && currentTenant.Id != null)
{
InvoiceMessages message = new InvoiceMessages
{
Expand All @@ -69,10 +56,10 @@ public async Task AddPaymentRequestsToInvoiceQueue(PaymentRequest paymentRequest
InvoiceNumber = paymentRequest.InvoiceNumber,
SupplierNumber = paymentRequest.SupplierNumber,
SiteNumber = paymentRequest.Site.Number,
TenantId = (Guid)_currentTenant.Id
TenantId = (Guid)currentTenant.Id
};

await _paymentQueueService.SendPaymentToInvoiceQueueAsync(message);
await paymentQueueService.SendPaymentToInvoiceQueueAsync(message);
}
}
catch (Exception ex)
Expand All @@ -93,21 +80,21 @@ public async Task ManuallyAddPaymentRequestsToReconciliationQueue(List<PaymentRe
InvoiceNumber = paymentRequest.InvoiceNumber,
SupplierNumber = paymentRequest.SupplierNumber,
SiteNumber = paymentRequest.Site?.Number ?? string.Empty,
TenantId = paymentRequest.TenantId ?? _currentTenant.Id!.Value
TenantId = paymentRequest.TenantId ?? currentTenant.Id!.Value
};

await _paymentQueueService.SendPaymentToReconciliationQueueAsync(reconcilePaymentMessage);
await paymentQueueService.SendPaymentToReconciliationQueueAsync(reconcilePaymentMessage);
}
}

public async Task AddPaymentRequestsToReconciliationQueue()
{
var tenants = await _tenantRepository.GetListAsync();
var tenants = await tenantRepository.GetListAsync();
foreach (var tenantId in tenants.Select(tenant => tenant.Id))
{
using (_currentTenant.Change(tenantId))
using (currentTenant.Change(tenantId))
{
List<PaymentRequest> paymentRequests = await _paymentRequestsRepository.GetPaymentRequestsBySentToCasStatusAsync();
List<PaymentRequest> paymentRequests = await paymentRequestsRepository.GetPaymentRequestsBySentToCasStatusAsync();
foreach (PaymentRequest paymentRequest in paymentRequests)
{
ReconcilePaymentMessages reconcilePaymentMessage = new ReconcilePaymentMessages
Expand All @@ -120,52 +107,60 @@ public async Task AddPaymentRequestsToReconciliationQueue()
TenantId = tenantId
};

await _paymentQueueService.SendPaymentToReconciliationQueueAsync(reconcilePaymentMessage);
await paymentQueueService.SendPaymentToReconciliationQueueAsync(reconcilePaymentMessage);
}
}
}
}

/// <summary>
/// Updates payment request status from CAS integration results.
/// Tenant context and audit scope are already established by the caller
/// (via <see cref="QueueConsumerHandler{TMessageConsumer,TQueueMessage}"/>);
/// this method only needs to own its unit of work.
/// </summary>
public async Task<PaymentRequest?> UpdatePaymentRequestStatus(Guid TenantId, Guid PaymentRequestId, CasPaymentSearchResult result)
{
PaymentRequest? paymentReqeust = null;
if (TenantId != Guid.Empty)
if (TenantId == Guid.Empty)
{
using (_currentTenant.Change(TenantId))
{
try
{
using var uow = _unitOfWorkManager.Begin(true, false);
paymentReqeust = await _paymentRequestsRepository.GetAsync(PaymentRequestId);
if (paymentReqeust != null)
{
if(paymentReqeust.InvoiceStatus == CasPaymentRequestStatus.NotFound && result.InvoiceStatus == CasPaymentRequestStatus.NotFound)
{
result.InvoiceStatus = CasPaymentRequestStatus.NotFound+"2";
}

paymentReqeust.SetInvoiceStatus(result.InvoiceStatus ?? "");
paymentReqeust.SetPaymentStatus(result.PaymentStatus ?? "");
paymentReqeust.SetPaymentDate(result.PaymentDate ?? "");
paymentReqeust.SetPaymentNumber(result.PaymentNumber ?? "");
if(result.InvoiceStatus != null)
{
paymentReqeust.SetCasHttpStatusCode((int)System.Net.HttpStatusCode.OK);
paymentReqeust.SetCasResponse("SUCCEEDED");
}

await _paymentRequestsRepository.UpdateAsync(paymentReqeust, autoSave: false);
await uow.SaveChangesAsync();
}
}
catch (Exception ex)
{
string ExceptionMessage = ex.Message;
Logger.LogInformation(ex, "UpdatePaymentRequestStatus: Error updating payment request: {ExceptionMessage}", ExceptionMessage);
}
}
return null;
}

using var uow = unitOfWorkManager.Begin(requiresNew: true, isTransactional: true);

var paymentRequest = await paymentRequestsRepository.GetAsync(PaymentRequestId);

UpdatePaymentRequestFromCasResult(paymentRequest, result);

await paymentRequestsRepository.UpdateAsync(paymentRequest, autoSave: false);

// CompleteAsync commits the transaction and calls SaveChangesAsync,
// which triggers AbpDbContext to collect entity changes into the active audit log.
// The audit log is then persisted by QueueConsumerHandler after ConsumeAsync returns.
await uow.CompleteAsync();

return paymentRequest;
}

private static void UpdatePaymentRequestFromCasResult(PaymentRequest paymentRequest, CasPaymentSearchResult result)
{
// Handle duplicate NotFound status by appending "2"
if (paymentRequest.InvoiceStatus == CasPaymentRequestStatus.NotFound &&
result.InvoiceStatus == CasPaymentRequestStatus.NotFound)
{
result.InvoiceStatus = CasPaymentRequestStatus.NotFound + "2";
}

paymentRequest.SetInvoiceStatus(result.InvoiceStatus ?? "");
paymentRequest.SetPaymentStatus(result.PaymentStatus ?? "");
paymentRequest.SetPaymentDate(result.PaymentDate ?? "");
paymentRequest.SetPaymentNumber(result.PaymentNumber ?? "");

if (result.InvoiceStatus != null)
{
paymentRequest.SetCasHttpStatusCode((int)System.Net.HttpStatusCode.OK);
paymentRequest.SetCasResponse("SUCCEEDED");
}
return paymentReqeust;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Unity.Payments.PaymentRequests.Notifications;
using Unity.Modules.Shared.Auditing;

namespace Unity.Payments;

[DependsOn(
typeof(UnityAuditingOverrideModule),
typeof(AbpVirtualFileSystemModule),
typeof(AbpDddApplicationModule),
typeof(AbpAutoMapperModule),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Unity.Modules.Shared.Constants;
using Unity.Modules.Shared.Utils;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
using Volo.Abp.Users;

namespace Unity.Modules.Shared.Auditing;

/// <summary>
/// Custom audit property setter that ensures background jobs have proper user context.
/// With proper BackgroundJobContext setup, ABP should populate most values automatically.
/// This provides a safety net fallback using reflection for readonly properties.
/// </summary>
public class BackgroundJobAuditPropertySetter : AuditPropertySetter, ITransientDependency
{
public BackgroundJobAuditPropertySetter(ICurrentUser currentUser, ICurrentTenant currentTenant, IClock clock)
: base(currentUser, currentTenant, clock)
{
}

public override void SetCreationProperties(object targetObject)
{
// Call base first to let ABP try to set properties
base.SetCreationProperties(targetObject);

// If in background job context and ABP hasn't set creator, use background job user
if (BackgroundJobExecutionContext.IsActive &&
targetObject is ICreationAuditedObject createdObject &&
createdObject.CreatorId == null)
{
var propertyInfo = targetObject.GetType().GetProperty(nameof(ICreationAuditedObject.CreatorId));
if (propertyInfo != null && propertyInfo.CanWrite)
{
propertyInfo.SetValue(targetObject, BackgroundJobConstants.BackgroundJobPersonId);
}
}
}

public override void SetModificationProperties(object targetObject)
{
// Call base first to let ABP try to set properties
base.SetModificationProperties(targetObject);

// If in background job context and ABP hasn't set modifier, use background job user
if (BackgroundJobExecutionContext.IsActive &&
targetObject is IModificationAuditedObject modifiedObject &&
modifiedObject.LastModifierId == null)
{
var propertyInfo = targetObject.GetType().GetProperty(nameof(IModificationAuditedObject.LastModifierId));
if (propertyInfo != null && propertyInfo.CanWrite)
{
propertyInfo.SetValue(targetObject, BackgroundJobConstants.BackgroundJobPersonId);
}
}
}
}
Loading
Loading