Skip to content
Merged
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
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;

namespace Unity.Payments.PaymentRequests
{
public interface IPaymentBulkActionsAppService : IApplicationService
{
Task<StorePaymentIdsResultDto> StorePaymentIdsAsync(StorePaymentIdsRequestDto input);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;

namespace Unity.Payments.PaymentRequests
{
public class StorePaymentIdsRequestDto
{
public List<Guid> PaymentRequestIds { get; set; } = new List<Guid>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Unity.Payments.PaymentRequests
{
public class StorePaymentIdsResultDto
{
public string CacheKey { get; set; } = string.Empty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public interface IPaymentTagAppService : IApplicationService
{
Task<IList<PaymentTagDto>> GetListAsync();
Task<IList<PaymentTagDto>> GetListWithPaymentRequestIdsAsync(List<Guid> ids);
Task<IList<PaymentTagDto>> GetListWithCacheKeyAsync(string cacheKey);
Task<List<PaymentTagDto>> AssignTagsAsync(AssignPaymentTagDto input);
Task<PaymentTagDto?> GetPaymentTagsAsync(Guid id);
Task<PagedResultDto<TagSummaryCountDto>> GetTagSummaryAsync();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;

namespace Unity.Payments.PaymentRequests
{
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(PaymentBulkActionsAppService), typeof(IPaymentBulkActionsAppService))]
[Authorize]
public class PaymentBulkActionsAppService : PaymentsAppService, IPaymentBulkActionsAppService
{
private readonly PaymentIdsCacheService _cacheService;

public PaymentBulkActionsAppService(PaymentIdsCacheService cacheService)
{
_cacheService = cacheService;
}

/// <summary>
/// Stores payment request IDs in distributed cache for bulk operations
/// </summary>
/// <param name="input">Request containing list of payment request IDs</param>
/// <returns>Cache key to retrieve the stored IDs</returns>
public async Task<StorePaymentIdsResultDto> StorePaymentIdsAsync(StorePaymentIdsRequestDto input)
{
if (input == null || input.PaymentRequestIds == null || input.PaymentRequestIds.Count == 0)
{
throw new UserFriendlyException("No payment request IDs provided");
}

try
{
var cacheKey = await _cacheService.StorePaymentIdsAsync(input.PaymentRequestIds);

Logger.LogInformation(
"User {UserId} stored {Count} payment request IDs for bulk operation with cache key: {CacheKey}",
CurrentUser?.Id,
input.PaymentRequestIds.Count,
cacheKey);

return new StorePaymentIdsResultDto
{
CacheKey = cacheKey
};
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to store payment request IDs for bulk operation");
throw new UserFriendlyException("Failed to prepare bulk operation. Please try again.");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;

namespace Unity.Payments.PaymentRequests
{
public class PaymentIdsCacheService : ITransientDependency
{
private readonly IDistributedCache<List<Guid>, string> _cache;
private readonly ILogger<PaymentIdsCacheService> _logger;
private const string CACHE_KEY_PREFIX = "BulkAction:PaymentRequestIds:";
private const int CACHE_EXPIRATION_MINUTES = 5;

public PaymentIdsCacheService(
IDistributedCache<List<Guid>, string> cache,
ILogger<PaymentIdsCacheService> logger)
{
_cache = cache;
_logger = logger;
}

/// <summary>
/// Stores payment request IDs in distributed cache and returns a unique cache key
/// </summary>
/// <param name="paymentRequestIds">List of payment request IDs to store</param>
/// <returns>Unique cache key to retrieve the data</returns>
public async Task<string> StorePaymentIdsAsync(List<Guid> paymentRequestIds)
{
if (paymentRequestIds == null || paymentRequestIds.Count == 0)
{
throw new ArgumentException("Payment request IDs list cannot be null or empty", nameof(paymentRequestIds));
}

var cacheKey = GenerateCacheKey();

try
{
await _cache.SetAsync(
cacheKey,
paymentRequestIds,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CACHE_EXPIRATION_MINUTES)
});

_logger.LogInformation(
"Stored {Count} payment request IDs in cache with key: {CacheKey}",
paymentRequestIds.Count,
cacheKey);

return cacheKey;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to store payment request IDs in cache");
throw;
}
}

/// <summary>
/// Retrieves payment request IDs from distributed cache using the cache key
/// </summary>
/// <param name="cacheKey">Cache key returned from StorePaymentIdsAsync</param>
/// <returns>List of payment request IDs, or null if not found/expired</returns>
public async Task<List<Guid>?> GetPaymentIdsAsync(string cacheKey)
{
if (string.IsNullOrWhiteSpace(cacheKey))
{
_logger.LogWarning("Cache key is null or empty");
return null;
}

try
{
var paymentRequestIds = await _cache.GetAsync(cacheKey);

if (paymentRequestIds == null)
{
_logger.LogWarning("No data found for cache key: {CacheKey} (expired or invalid)", cacheKey);
}
else
{
_logger.LogInformation(
"Retrieved {Count} payment request IDs from cache with key: {CacheKey}",
paymentRequestIds.Count,
cacheKey);
}

return paymentRequestIds;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to retrieve payment request IDs from cache with key: {CacheKey}", cacheKey);
return null;
}
}

/// <summary>
/// Removes payment request IDs from distributed cache
/// </summary>
/// <param name="cacheKey">Cache key to remove</param>
public async Task RemoveAsync(string cacheKey)
{
if (string.IsNullOrWhiteSpace(cacheKey))
{
return;
}

try
{
await _cache.RemoveAsync(cacheKey);
_logger.LogInformation("Removed cache entry with key: {CacheKey}", cacheKey);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to remove cache entry with key: {CacheKey}", cacheKey);
// Don't throw - cache removal failure is not critical
}
}

/// <summary>
/// Generates a unique cache key for storing payment request IDs
/// </summary>
/// <returns>Unique cache key with prefix</returns>
private static string GenerateCacheKey()
{
return $"{CACHE_KEY_PREFIX}{Guid.NewGuid()}";
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Unity.Modules.Shared;
using Unity.Payments.Domain.PaymentTags;
using Unity.Payments.Events;
using Unity.Payments.PaymentRequests;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus.Local;
Expand All @@ -21,10 +24,16 @@ public class PaymentTagAppService : PaymentsAppService, IPaymentTagAppService
{
private readonly IPaymentTagRepository _paymentTagRepository;
private readonly ILocalEventBus _localEventBus;
public PaymentTagAppService(IPaymentTagRepository paymentTagRepository, ILocalEventBus localEventBus)
private readonly PaymentIdsCacheService _cacheService;

public PaymentTagAppService(
IPaymentTagRepository paymentTagRepository,
ILocalEventBus localEventBus,
PaymentIdsCacheService cacheService)
{
_paymentTagRepository = paymentTagRepository;
_localEventBus = localEventBus;
_cacheService = cacheService;
}
public async Task<IList<PaymentTagDto>> GetListAsync()
{
Expand All @@ -42,6 +51,40 @@ public async Task<IList<PaymentTagDto>> GetListWithPaymentRequestIdsAsync(List<G

return ObjectMapper.Map<List<PaymentTag>, List<PaymentTagDto>>(tags);
}

public async Task<IList<PaymentTagDto>> GetListWithCacheKeyAsync(string cacheKey)
{
if (string.IsNullOrWhiteSpace(cacheKey))
{
throw new UserFriendlyException("Cache key is required");
}

try
{
// Retrieve payment IDs from cache
var paymentIds = await _cacheService.GetPaymentIdsAsync(cacheKey);

if (paymentIds == null || paymentIds.Count == 0)
{
Logger.LogWarning("Cache key expired or invalid: {CacheKey}", cacheKey);
throw new UserFriendlyException("The session has expired. Please select payments and try again.");
}

Logger.LogInformation("Retrieved {Count} payment IDs from cache for tag list", paymentIds.Count);

// Use the existing method to get tags for these payment IDs
return await GetListWithPaymentRequestIdsAsync(paymentIds);
}
catch (UserFriendlyException)
{
throw;
}
catch (Exception ex)
{
Logger.LogError(ex, "Error retrieving payment tags with cache key: {CacheKey}", cacheKey);
throw new UserFriendlyException("Failed to retrieve payment tags. Please try again.");
}
}
public async Task<PaymentTagDto?> GetPaymentTagsAsync(Guid id)
{
var paymentTags = await _paymentTagRepository.FirstOrDefaultAsync(s => s.PaymentRequestId == id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,33 @@
}

<abp-modal-body>
<abp-card class="pb-0">
<abp-card-body class="pb-0 mb-0">
<abp-row class="m-0 p-1">
<abp-column size="_4">
<label for="@Model.PaymentGroupings?.Count" class="form-label unt-form-label-primary">@L["ApplicationPaymentRequest:NumberPayment"]</label>
<input type="text" class="form-control" id="ApplicationCount" value="@(Model.PaymentGroupings?.Sum(g => g.Items.Count) ?? 0)" disabled />
</abp-column>
<abp-column size="_4">
<label for="@Model.TotalAmount" class="form-label unt-form-label-primary">@L["ApplicationPaymentRequest:TotalAmount"]</label>
<input type="text" class="form-control totalAmount unity-currency-input" id="UpdateTotalAmount" value="@Model.TotalAmount" disabled />
</abp-column>
<abp-column size="_4">
<label for="Note" class="form-label unt-form-label-primary">@L["ApplicationPaymentRequest:BatchNote"]</label>
<input type="text" class="form-control" asp-for="Note" />
</abp-column>
</abp-row>
</abp-card-body>
</abp-card>
<abp-card>
@if (ViewData["Error"] != null)
{
<div class="alert alert-warning" role="alert">
<i class="fa fa-exclamation-triangle"></i> @ViewData["Error"]
</div>
}
else
{
<abp-card class="pb-0">
<abp-card-body class="pb-0 mb-0">
<abp-row class="m-0 p-1">
<abp-column size="_4">
<label for="@Model.PaymentGroupings?.Count" class="form-label unt-form-label-primary">@L["ApplicationPaymentRequest:NumberPayment"]</label>
<input type="text" class="form-control" id="ApplicationCount" value="@(Model.PaymentGroupings?.Sum(g => g.Items.Count) ?? 0)" disabled />
</abp-column>
<abp-column size="_4">
<label for="@Model.TotalAmount" class="form-label unt-form-label-primary">@L["ApplicationPaymentRequest:TotalAmount"]</label>
<input type="text" class="form-control totalAmount unity-currency-input" id="UpdateTotalAmount" value="@Model.TotalAmount" disabled />
</abp-column>
<abp-column size="_4">
<label for="Note" class="form-label unt-form-label-primary">@L["ApplicationPaymentRequest:BatchNote"]</label>
<input type="text" class="form-control" asp-for="Note" />
</abp-column>
</abp-row>
</abp-card-body>
</abp-card>
<abp-card>
<abp-card-body class="payment-card">
<abp-input id="PaymentThreshold" type="hidden" asp-for="PaymentThreshold" />
<abp-input id="UserPaymentThreshold" type="hidden" asp-for="UserPaymentThreshold" />
Expand Down Expand Up @@ -180,6 +188,7 @@
</abp-row>
</abp-card-body>
</abp-card>
}
</abp-modal-body>
<abp-modal-footer>
@if (Model.IsApproval)
Expand Down
Loading