diff --git a/k8s/staging/cms-config.yaml b/k8s/staging/cms-config.yaml index 1acf1dd..c9c5562 100644 --- a/k8s/staging/cms-config.yaml +++ b/k8s/staging/cms-config.yaml @@ -24,6 +24,8 @@ stringData: "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", "UseSandbox": true }, + "CmsBaseUrl": "https://cms.se.kbs1.ir", + "FrontOfficeBaseUrl": "https://frontoffice.se.kbs1.ir", "FMS": { "Address": "https://dl.afrino.co" }, diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs index 15006e1..0735b07 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs @@ -1,4 +1,5 @@ using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Helpers; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Application.Common.Models; using CMSMicroservice.Domain.Common; @@ -272,6 +273,7 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler x.User) + .Include(x => x.LastPackage) .ApplyOrder(sortBy: request.SortBy) .AsNoTracking() .AsQueryable(); @@ -35,8 +36,10 @@ public class GetAllClubMembershipsQueryHandler : IRequestHandler -/// Query برای دریافت تاریخچه تغییرات عضویت باشگاه -/// public record GetClubMembershipHistoryQuery : IRequest { - /// - /// شناسه عضویت (اختیاری) - /// - public long? MembershipId { get; init; } - - /// - /// شناسه کاربر (اختیاری) - /// - public long? UserId { get; init; } - - /// - /// موقعیت صفحه‌بندی - /// public PaginationState? PaginationState { get; init; } - - /// - /// مرتب‌سازی بر اساس - /// public string? SortBy { get; init; } + public GetClubMembershipHistoryFilter? Filter { get; init; } +} + +public class GetClubMembershipHistoryFilter +{ + public long? UserId { get; set; } + public long? ClubMembershipId { get; set; } + public ClubMembershipAction? Action { get; set; } + public DateTime? CreatedFrom { get; set; } + public DateTime? CreatedTo { get; set; } } diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembershipHistory/GetClubMembershipHistoryQueryHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembershipHistory/GetClubMembershipHistoryQueryHandler.cs index 32c9d5a..47bfc6e 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembershipHistory/GetClubMembershipHistoryQueryHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembershipHistory/GetClubMembershipHistoryQueryHandler.cs @@ -12,20 +12,30 @@ public class GetClubMembershipHistoryQueryHandler : IRequestHandler Handle(GetClubMembershipHistoryQuery request, CancellationToken cancellationToken) { var query = _context.ClubMembershipHistories + .Include(x => x.ClubMembership) + .ThenInclude(m => m!.User) .AsNoTracking() .AsQueryable(); - if (request.MembershipId.HasValue) + if (request.Filter != null) { - query = query.Where(x => x.ClubMembershipId == request.MembershipId.Value); + if (request.Filter.ClubMembershipId.HasValue) + query = query.Where(x => x.ClubMembershipId == request.Filter.ClubMembershipId.Value); + + if (request.Filter.UserId.HasValue) + query = query.Where(x => x.UserId == request.Filter.UserId.Value); + + if (request.Filter.Action.HasValue) + query = query.Where(x => x.Action == request.Filter.Action.Value); + + if (request.Filter.CreatedFrom.HasValue) + query = query.Where(x => x.Created >= request.Filter.CreatedFrom.Value); + + if (request.Filter.CreatedTo.HasValue) + query = query.Where(x => x.Created <= request.Filter.CreatedTo.Value); } - if (request.UserId.HasValue) - { - query = query.Where(x => x.UserId == request.UserId.Value); - } - - query = query.ApplyOrder(sortBy: request.SortBy ?? "Created"); // پیش‌فرض: جدیدترین اول + query = query.ApplyOrder(sortBy: request.SortBy ?? "-Created"); var meta = await query.GetMetaData(request.PaginationState, cancellationToken); @@ -36,6 +46,9 @@ public class GetClubMembershipHistoryQueryHandler : IRequestHandler x.MembershipId) - .GreaterThan(0) - .WithMessage("شناسه عضویت معتبر نیست") - .When(x => x.MembershipId.HasValue); - - RuleFor(x => x.UserId) - .GreaterThan(0) - .WithMessage("شناسه کاربر معتبر نیست") - .When(x => x.UserId.HasValue); - - RuleFor(x => x) - .Must(x => x.MembershipId.HasValue || x.UserId.HasValue) - .WithMessage("حداقل یکی از MembershipId یا UserId باید مقداردهی شود"); + // Admin report: filters are all optional } - - public Func>> ValidateValue => async (model, propertyName) => - { - var result = await ValidateAsync( - ValidationContext.CreateWithOptions( - (GetClubMembershipHistoryQuery)model, - x => x.IncludeProperties(propertyName))); - - if (result.IsValid) - return Array.Empty(); - - return result.Errors.Select(e => e.ErrorMessage); - }; } diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembershipHistory/GetClubMembershipHistoryResponseDto.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembershipHistory/GetClubMembershipHistoryResponseDto.cs index 1411d7b..da661a9 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembershipHistory/GetClubMembershipHistoryResponseDto.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Queries/GetClubMembershipHistory/GetClubMembershipHistoryResponseDto.cs @@ -11,6 +11,7 @@ public class GetClubMembershipHistoryResponseModel public long Id { get; set; } public long ClubMembershipId { get; set; } public long UserId { get; set; } + public string UserName { get; set; } = string.Empty; public bool OldIsActive { get; set; } public bool NewIsActive { get; set; } public long? OldInitialContribution { get; set; } diff --git a/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetAllClubMembershipCycleByFilter/ClubMembershipCycleFilterExtensions.cs b/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetAllClubMembershipCycleByFilter/ClubMembershipCycleFilterExtensions.cs new file mode 100644 index 0000000..646c173 --- /dev/null +++ b/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetAllClubMembershipCycleByFilter/ClubMembershipCycleFilterExtensions.cs @@ -0,0 +1,46 @@ +using CMSMicroservice.Domain.Entities.Club; + +namespace CMSMicroservice.Application.ClubMembershipCycleCQ.Queries.GetAllClubMembershipCycleByFilter; + +public static class ClubMembershipCycleFilterExtensions +{ + public static IQueryable ApplyClubMembershipCycleFilter( + this IQueryable query, + GetAllClubMembershipCycleByFilterFilter? filter) + { + if (filter == null) return query; + + if (filter.UserId.HasValue) + query = query.Where(x => x.UserId == filter.UserId.Value); + + if (filter.IsCurrentCycle.HasValue) + query = query.Where(x => x.IsCurrentCycle == filter.IsCurrentCycle.Value); + + if (filter.PackageId.HasValue) + query = query.Where(x => x.PackageId == filter.PackageId.Value); + + if (filter.CycleNumber.HasValue) + query = query.Where(x => x.CycleNumber == filter.CycleNumber.Value); + + if (filter.PurchaseMethod.HasValue) + query = query.Where(x => x.PurchaseMethod == filter.PurchaseMethod.Value); + + if (filter.MagicState.HasValue) + { + query = filter.MagicState.Value switch + { + 1 => query.Where(x => x.MagicStartedAt != null && x.MagicCompletedAt == null), + 2 => query.Where(x => x.MagicCompletedAt != null), + _ => query.Where(x => x.MagicStartedAt == null && x.MagicCompletedAt == null) + }; + } + + if (filter.PackagePurchasedFrom.HasValue) + query = query.Where(x => x.PackagePurchasedAt >= filter.PackagePurchasedFrom.Value); + + if (filter.PackagePurchasedTo.HasValue) + query = query.Where(x => x.PackagePurchasedAt <= filter.PackagePurchasedTo.Value); + + return query; + } +} diff --git a/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetAllClubMembershipCycleByFilter/GetAllClubMembershipCycleByFilterQuery.cs b/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetAllClubMembershipCycleByFilter/GetAllClubMembershipCycleByFilterQuery.cs new file mode 100644 index 0000000..a79d1a4 --- /dev/null +++ b/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetAllClubMembershipCycleByFilter/GetAllClubMembershipCycleByFilterQuery.cs @@ -0,0 +1,47 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.ClubMembershipCycleCQ.Queries.GetAllClubMembershipCycleByFilter; + +public record GetAllClubMembershipCycleByFilterQuery : IRequest +{ + public PaginationState? PaginationState { get; init; } + public string? SortBy { get; init; } + public GetAllClubMembershipCycleByFilterFilter? Filter { get; init; } +} + +public class GetAllClubMembershipCycleByFilterFilter +{ + public long? UserId { get; set; } + public bool? IsCurrentCycle { get; set; } + public long? PackageId { get; set; } + public int? CycleNumber { get; set; } + public PackagePurchaseMethod? PurchaseMethod { get; set; } + /// 0=none, 1=active magic, 2=completed magic + public int? MagicState { get; set; } + public DateTime? PackagePurchasedFrom { get; set; } + public DateTime? PackagePurchasedTo { get; set; } +} + +public class GetAllClubMembershipCycleByFilterResponseDto +{ + public MetaData MetaData { get; set; } + public List? Models { get; set; } +} + +public class GetAllClubMembershipCycleByFilterResponseModel +{ + public long Id { get; set; } + public long UserId { get; set; } + public string UserName { get; set; } = string.Empty; + public long ClubMembershipId { get; set; } + public int CycleNumber { get; set; } + public long PackageId { get; set; } + public string PackageName { get; set; } = string.Empty; + public long PackageAmount { get; set; } + public PackagePurchaseMethod PurchaseMethod { get; set; } + public DateTime PackagePurchasedAt { get; set; } + public DateTime? MagicStartedAt { get; set; } + public DateTime? MagicCompletedAt { get; set; } + public bool IsCurrentCycle { get; set; } + public DateTimeOffset Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetAllClubMembershipCycleByFilter/GetAllClubMembershipCycleByFilterQueryHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetAllClubMembershipCycleByFilter/GetAllClubMembershipCycleByFilterQueryHandler.cs new file mode 100644 index 0000000..9434541 --- /dev/null +++ b/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetAllClubMembershipCycleByFilter/GetAllClubMembershipCycleByFilterQueryHandler.cs @@ -0,0 +1,49 @@ +namespace CMSMicroservice.Application.ClubMembershipCycleCQ.Queries.GetAllClubMembershipCycleByFilter; + +public class GetAllClubMembershipCycleByFilterQueryHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllClubMembershipCycleByFilterQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetAllClubMembershipCycleByFilterQuery request, + CancellationToken cancellationToken) + { + var query = _context.ClubMembershipCycles + .Include(x => x.User) + .Include(x => x.Package) + .AsNoTracking() + .ApplyClubMembershipCycleFilter(request.Filter) + .ApplyOrder(sortBy: request.SortBy ?? "-PackagePurchasedAt"); + + return new GetAllClubMembershipCycleByFilterResponseDto + { + MetaData = await query.GetMetaData(request.PaginationState, cancellationToken), + Models = await query + .PaginatedListAsync(paginationState: request.PaginationState) + .Select(x => new GetAllClubMembershipCycleByFilterResponseModel + { + Id = x.Id, + UserId = x.UserId, + UserName = x.User != null ? $"{x.User.FirstName} {x.User.LastName}" : string.Empty, + ClubMembershipId = x.ClubMembershipId, + CycleNumber = x.CycleNumber, + PackageId = x.PackageId, + PackageName = x.Package != null ? x.Package.Title : string.Empty, + PackageAmount = x.PackageAmount, + PurchaseMethod = x.PurchaseMethod, + PackagePurchasedAt = x.PackagePurchasedAt, + MagicStartedAt = x.MagicStartedAt, + MagicCompletedAt = x.MagicCompletedAt, + IsCurrentCycle = x.IsCurrentCycle, + Created = x.Created + }) + .ToListAsync(cancellationToken) + }; + } +} diff --git a/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetClubMembershipCycleSummary/GetClubMembershipCycleSummaryQuery.cs b/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetClubMembershipCycleSummary/GetClubMembershipCycleSummaryQuery.cs new file mode 100644 index 0000000..b5953f9 --- /dev/null +++ b/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetClubMembershipCycleSummary/GetClubMembershipCycleSummaryQuery.cs @@ -0,0 +1,13 @@ +namespace CMSMicroservice.Application.ClubMembershipCycleCQ.Queries.GetClubMembershipCycleSummary; + +public record GetClubMembershipCycleSummaryQuery : IRequest +{ + public GetAllClubMembershipCycleByFilter.GetAllClubMembershipCycleByFilterFilter? Filter { get; init; } +} + +public class GetClubMembershipCycleSummaryResponseDto +{ + public long TotalCount { get; set; } + public long CurrentCycleCount { get; set; } + public long MagicActiveCount { get; set; } +} diff --git a/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetClubMembershipCycleSummary/GetClubMembershipCycleSummaryQueryHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetClubMembershipCycleSummary/GetClubMembershipCycleSummaryQueryHandler.cs new file mode 100644 index 0000000..7fe755a --- /dev/null +++ b/src/CMSMicroservice.Application/ClubMembershipCycleCQ/Queries/GetClubMembershipCycleSummary/GetClubMembershipCycleSummaryQueryHandler.cs @@ -0,0 +1,36 @@ +using CMSMicroservice.Application.ClubMembershipCycleCQ.Queries.GetAllClubMembershipCycleByFilter; + +namespace CMSMicroservice.Application.ClubMembershipCycleCQ.Queries.GetClubMembershipCycleSummary; + +public class GetClubMembershipCycleSummaryQueryHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetClubMembershipCycleSummaryQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetClubMembershipCycleSummaryQuery request, + CancellationToken cancellationToken) + { + var query = _context.ClubMembershipCycles + .AsNoTracking() + .ApplyClubMembershipCycleFilter(request.Filter); + + var totalCount = await query.LongCountAsync(cancellationToken); + var currentCycleCount = await query.LongCountAsync(x => x.IsCurrentCycle, cancellationToken); + var magicActiveCount = await query.LongCountAsync( + x => x.MagicStartedAt != null && x.MagicCompletedAt == null, + cancellationToken); + + return new GetClubMembershipCycleSummaryResponseDto + { + TotalCount = totalCount, + CurrentCycleCount = currentCycleCount, + MagicActiveCount = magicActiveCount + }; + } +} diff --git a/src/CMSMicroservice.Application/ClubMembershipCycleHistoryCQ/Queries/GetAllClubMembershipCycleHistoryByFilter/ClubMembershipCycleHistoryFilterExtensions.cs b/src/CMSMicroservice.Application/ClubMembershipCycleHistoryCQ/Queries/GetAllClubMembershipCycleHistoryByFilter/ClubMembershipCycleHistoryFilterExtensions.cs new file mode 100644 index 0000000..72bc414 --- /dev/null +++ b/src/CMSMicroservice.Application/ClubMembershipCycleHistoryCQ/Queries/GetAllClubMembershipCycleHistoryByFilter/ClubMembershipCycleHistoryFilterExtensions.cs @@ -0,0 +1,33 @@ +using CMSMicroservice.Domain.Entities.History; + +namespace CMSMicroservice.Application.ClubMembershipCycleHistoryCQ.Queries.GetAllClubMembershipCycleHistoryByFilter; + +public static class ClubMembershipCycleHistoryFilterExtensions +{ + public static IQueryable ApplyClubMembershipCycleHistoryFilter( + this IQueryable query, + GetAllClubMembershipCycleHistoryByFilterFilter? filter) + { + if (filter == null) return query; + + if (filter.UserId.HasValue) + query = query.Where(x => x.UserId == filter.UserId.Value); + + if (filter.ClubMembershipCycleId.HasValue) + query = query.Where(x => x.ClubMembershipCycleId == filter.ClubMembershipCycleId.Value); + + if (filter.CycleNumber.HasValue) + query = query.Where(x => x.CycleNumber == filter.CycleNumber.Value); + + if (filter.Action.HasValue) + query = query.Where(x => x.Action == filter.Action.Value); + + if (filter.CreatedFrom.HasValue) + query = query.Where(x => x.Created >= filter.CreatedFrom.Value); + + if (filter.CreatedTo.HasValue) + query = query.Where(x => x.Created <= filter.CreatedTo.Value); + + return query; + } +} diff --git a/src/CMSMicroservice.Application/ClubMembershipCycleHistoryCQ/Queries/GetAllClubMembershipCycleHistoryByFilter/GetAllClubMembershipCycleHistoryByFilterQuery.cs b/src/CMSMicroservice.Application/ClubMembershipCycleHistoryCQ/Queries/GetAllClubMembershipCycleHistoryByFilter/GetAllClubMembershipCycleHistoryByFilterQuery.cs new file mode 100644 index 0000000..dfb458d --- /dev/null +++ b/src/CMSMicroservice.Application/ClubMembershipCycleHistoryCQ/Queries/GetAllClubMembershipCycleHistoryByFilter/GetAllClubMembershipCycleHistoryByFilterQuery.cs @@ -0,0 +1,45 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.ClubMembershipCycleHistoryCQ.Queries.GetAllClubMembershipCycleHistoryByFilter; + +public record GetAllClubMembershipCycleHistoryByFilterQuery : IRequest +{ + public PaginationState? PaginationState { get; init; } + public string? SortBy { get; init; } + public GetAllClubMembershipCycleHistoryByFilterFilter? Filter { get; init; } +} + +public class GetAllClubMembershipCycleHistoryByFilterFilter +{ + public long? UserId { get; set; } + public long? ClubMembershipCycleId { get; set; } + public int? CycleNumber { get; set; } + public ClubMembershipCycleAction? Action { get; set; } + public DateTime? CreatedFrom { get; set; } + public DateTime? CreatedTo { get; set; } +} + +public class GetAllClubMembershipCycleHistoryByFilterResponseDto +{ + public MetaData MetaData { get; set; } + public List? Models { get; set; } +} + +public class GetAllClubMembershipCycleHistoryByFilterResponseModel +{ + public long Id { get; set; } + public long ClubMembershipCycleId { get; set; } + public long UserId { get; set; } + public string UserName { get; set; } = string.Empty; + public int CycleNumber { get; set; } + public bool OldIsCurrentCycle { get; set; } + public bool NewIsCurrentCycle { get; set; } + public DateTime? OldMagicStartedAt { get; set; } + public DateTime? NewMagicStartedAt { get; set; } + public DateTime? OldMagicCompletedAt { get; set; } + public DateTime? NewMagicCompletedAt { get; set; } + public ClubMembershipCycleAction Action { get; set; } + public string? PerformedBy { get; set; } + public string? Reason { get; set; } + public DateTimeOffset Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/ClubMembershipCycleHistoryCQ/Queries/GetAllClubMembershipCycleHistoryByFilter/GetAllClubMembershipCycleHistoryByFilterQueryHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCycleHistoryCQ/Queries/GetAllClubMembershipCycleHistoryByFilter/GetAllClubMembershipCycleHistoryByFilterQueryHandler.cs new file mode 100644 index 0000000..97ede8d --- /dev/null +++ b/src/CMSMicroservice.Application/ClubMembershipCycleHistoryCQ/Queries/GetAllClubMembershipCycleHistoryByFilter/GetAllClubMembershipCycleHistoryByFilterQueryHandler.cs @@ -0,0 +1,52 @@ +namespace CMSMicroservice.Application.ClubMembershipCycleHistoryCQ.Queries.GetAllClubMembershipCycleHistoryByFilter; + +public class GetAllClubMembershipCycleHistoryByFilterQueryHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllClubMembershipCycleHistoryByFilterQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetAllClubMembershipCycleHistoryByFilterQuery request, + CancellationToken cancellationToken) + { + var query = _context.ClubMembershipCycleHistories + .Include(x => x.ClubMembershipCycle) + .ThenInclude(c => c!.User) + .AsNoTracking() + .ApplyClubMembershipCycleHistoryFilter(request.Filter) + .ApplyOrder(sortBy: request.SortBy ?? "-Created"); + + return new GetAllClubMembershipCycleHistoryByFilterResponseDto + { + MetaData = await query.GetMetaData(request.PaginationState, cancellationToken), + Models = await query + .PaginatedListAsync(paginationState: request.PaginationState) + .Select(x => new GetAllClubMembershipCycleHistoryByFilterResponseModel + { + Id = x.Id, + ClubMembershipCycleId = x.ClubMembershipCycleId, + UserId = x.UserId, + UserName = x.ClubMembershipCycle != null && x.ClubMembershipCycle.User != null + ? $"{x.ClubMembershipCycle.User.FirstName} {x.ClubMembershipCycle.User.LastName}" + : string.Empty, + CycleNumber = x.CycleNumber, + OldIsCurrentCycle = x.OldIsCurrentCycle, + NewIsCurrentCycle = x.NewIsCurrentCycle, + OldMagicStartedAt = x.OldMagicStartedAt, + NewMagicStartedAt = x.NewMagicStartedAt, + OldMagicCompletedAt = x.OldMagicCompletedAt, + NewMagicCompletedAt = x.NewMagicCompletedAt, + Action = x.Action, + PerformedBy = x.PerformedBy, + Reason = x.Reason, + Created = x.Created + }) + .ToListAsync(cancellationToken) + }; + } +} diff --git a/src/CMSMicroservice.Application/Common/Extensions/SortByExtensions.cs b/src/CMSMicroservice.Application/Common/Extensions/SortByExtensions.cs index 3f0d9c8..439e9a5 100644 --- a/src/CMSMicroservice.Application/Common/Extensions/SortByExtensions.cs +++ b/src/CMSMicroservice.Application/Common/Extensions/SortByExtensions.cs @@ -8,16 +8,31 @@ public static class SortByExtensions public static IQueryable ApplyOrder(this IQueryable source, string? sortBy) where TSource : BaseAuditableEntity { - // default sort approach if (sortBy is null or "") { - source = source.OrderByDescending(p => p.Created); - return source; + return source.OrderByDescending(p => p.Created); } - // sort using dynamic linq - source = source.OrderBy(sortBy); + return source.OrderBy(NormalizeSortBy(sortBy)); + } - return source; + /// + /// Dynamic LINQ treats "-Created" as unary minus on DateTime (invalid). + /// Project convention: leading '-' means descending sort. + /// + private static string NormalizeSortBy(string sortBy) + { + sortBy = sortBy.Trim(); + if (sortBy.StartsWith('-') && sortBy.Length > 1) + { + return $"{sortBy[1..]} descending"; + } + + if (sortBy.StartsWith('+') && sortBy.Length > 1) + { + return $"{sortBy[1..]} ascending"; + } + + return sortBy; } } diff --git a/src/CMSMicroservice.Application/Common/Helpers/ClubMembershipCycleHistoryWriter.cs b/src/CMSMicroservice.Application/Common/Helpers/ClubMembershipCycleHistoryWriter.cs new file mode 100644 index 0000000..8689374 --- /dev/null +++ b/src/CMSMicroservice.Application/Common/Helpers/ClubMembershipCycleHistoryWriter.cs @@ -0,0 +1,54 @@ +using CMSMicroservice.Domain.Entities.Club; +using CMSMicroservice.Domain.Entities.History; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.Common.Helpers; + +public static class ClubMembershipCycleHistoryWriter +{ + public static void WriteCycleStarted( + IApplicationDbContext context, + ClubMembershipCycle cycle, + string? performedBy = "System", + string? reason = null) + { + context.ClubMembershipCycleHistories.Add(new ClubMembershipCycleHistory + { + ClubMembershipCycleId = cycle.Id, + UserId = cycle.UserId, + CycleNumber = cycle.CycleNumber, + OldIsCurrentCycle = false, + NewIsCurrentCycle = cycle.IsCurrentCycle, + OldMagicStartedAt = null, + NewMagicStartedAt = cycle.MagicStartedAt, + OldMagicCompletedAt = null, + NewMagicCompletedAt = cycle.MagicCompletedAt, + Action = ClubMembershipCycleAction.Started, + PerformedBy = performedBy, + Reason = reason + }); + } + + public static void WriteCycleClosed( + IApplicationDbContext context, + ClubMembershipCycle cycle, + string? performedBy = "System", + string? reason = null) + { + context.ClubMembershipCycleHistories.Add(new ClubMembershipCycleHistory + { + ClubMembershipCycleId = cycle.Id, + UserId = cycle.UserId, + CycleNumber = cycle.CycleNumber, + OldIsCurrentCycle = true, + NewIsCurrentCycle = false, + OldMagicStartedAt = cycle.MagicStartedAt, + NewMagicStartedAt = cycle.MagicStartedAt, + OldMagicCompletedAt = cycle.MagicCompletedAt, + NewMagicCompletedAt = cycle.MagicCompletedAt, + Action = ClubMembershipCycleAction.Completed, + PerformedBy = performedBy, + Reason = reason ?? "دوره جدید جایگزین شد" + }); + } +} diff --git a/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetAllPaymentTransactionByFilter/GetAllPaymentTransactionByFilterQuery.cs b/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetAllPaymentTransactionByFilter/GetAllPaymentTransactionByFilterQuery.cs new file mode 100644 index 0000000..e9205d9 --- /dev/null +++ b/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetAllPaymentTransactionByFilter/GetAllPaymentTransactionByFilterQuery.cs @@ -0,0 +1,46 @@ +namespace CMSMicroservice.Application.PaymentTransactionCQ.Queries.GetAllPaymentTransactionByFilter; + +public record GetAllPaymentTransactionByFilterQuery : IRequest +{ + public PaginationState? PaginationState { get; init; } + public string? SortBy { get; init; } + public GetAllPaymentTransactionByFilterFilter? Filter { get; init; } +} + +public class GetAllPaymentTransactionByFilterFilter +{ + public long? UserId { get; set; } + public string? Mobile { get; set; } + public string? GatewayProvider { get; set; } + public bool? PaymentStatus { get; set; } + public string? Authority { get; set; } + public string? RefId { get; set; } + public string? OrderId { get; set; } + public DateTime? CreatedFrom { get; set; } + public DateTime? CreatedTo { get; set; } +} + +public class GetAllPaymentTransactionByFilterResponseDto +{ + public MetaData MetaData { get; set; } + public List? Models { get; set; } +} + +public class GetAllPaymentTransactionByFilterResponseModel +{ + public long Id { get; set; } + public long? UserId { get; set; } + public string? Mobile { get; set; } + public string GatewayProvider { get; set; } = string.Empty; + public long Amount { get; set; } + public string? Authority { get; set; } + public bool PaymentStatus { get; set; } + public int? VerificationStatusCode { get; set; } + public string? VerificationStatusMessage { get; set; } + public string? RefId { get; set; } + public string? CardPan { get; set; } + public string? OrderId { get; set; } + public long? TransactionId { get; set; } + public string? Description { get; set; } + public DateTimeOffset Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetAllPaymentTransactionByFilter/GetAllPaymentTransactionByFilterQueryHandler.cs b/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetAllPaymentTransactionByFilter/GetAllPaymentTransactionByFilterQueryHandler.cs new file mode 100644 index 0000000..57e3e0f --- /dev/null +++ b/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetAllPaymentTransactionByFilter/GetAllPaymentTransactionByFilterQueryHandler.cs @@ -0,0 +1,48 @@ +namespace CMSMicroservice.Application.PaymentTransactionCQ.Queries.GetAllPaymentTransactionByFilter; + +public class GetAllPaymentTransactionByFilterQueryHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllPaymentTransactionByFilterQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetAllPaymentTransactionByFilterQuery request, + CancellationToken cancellationToken) + { + var query = _context.PaymentTransactions + .AsNoTracking() + .ApplyPaymentTransactionFilter(request.Filter) + .ApplyOrder(sortBy: request.SortBy ?? "-Created"); + + return new GetAllPaymentTransactionByFilterResponseDto + { + MetaData = await query.GetMetaData(request.PaginationState, cancellationToken), + Models = await query + .PaginatedListAsync(paginationState: request.PaginationState) + .Select(x => new GetAllPaymentTransactionByFilterResponseModel + { + Id = x.Id, + UserId = x.UserId, + Mobile = x.Mobile, + GatewayProvider = x.GatewayProvider, + Amount = x.Amount, + Authority = x.Authority, + PaymentStatus = x.PaymentStatus, + VerificationStatusCode = x.VerificationStatusCode, + VerificationStatusMessage = x.VerificationStatusMessage, + RefId = x.RefId, + CardPan = x.CardPan, + OrderId = x.OrderId, + TransactionId = x.TransactionId, + Description = x.Description, + Created = x.Created + }) + .ToListAsync(cancellationToken) + }; + } +} diff --git a/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetAllPaymentTransactionByFilter/PaymentTransactionFilterExtensions.cs b/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetAllPaymentTransactionByFilter/PaymentTransactionFilterExtensions.cs new file mode 100644 index 0000000..93d14eb --- /dev/null +++ b/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetAllPaymentTransactionByFilter/PaymentTransactionFilterExtensions.cs @@ -0,0 +1,42 @@ +using CMSMicroservice.Domain.Entities.Payment; + +namespace CMSMicroservice.Application.PaymentTransactionCQ.Queries.GetAllPaymentTransactionByFilter; + +public static class PaymentTransactionFilterExtensions +{ + public static IQueryable ApplyPaymentTransactionFilter( + this IQueryable query, + GetAllPaymentTransactionByFilterFilter? filter) + { + if (filter == null) return query; + + if (filter.UserId.HasValue) + query = query.Where(x => x.UserId == filter.UserId.Value); + + if (!string.IsNullOrWhiteSpace(filter.Mobile)) + query = query.Where(x => x.Mobile != null && x.Mobile.Contains(filter.Mobile)); + + if (!string.IsNullOrWhiteSpace(filter.GatewayProvider)) + query = query.Where(x => x.GatewayProvider == filter.GatewayProvider); + + if (filter.PaymentStatus.HasValue) + query = query.Where(x => x.PaymentStatus == filter.PaymentStatus.Value); + + if (!string.IsNullOrWhiteSpace(filter.Authority)) + query = query.Where(x => x.Authority != null && x.Authority.Contains(filter.Authority)); + + if (!string.IsNullOrWhiteSpace(filter.RefId)) + query = query.Where(x => x.RefId != null && x.RefId.Contains(filter.RefId)); + + if (!string.IsNullOrWhiteSpace(filter.OrderId)) + query = query.Where(x => x.OrderId != null && x.OrderId.Contains(filter.OrderId)); + + if (filter.CreatedFrom.HasValue) + query = query.Where(x => x.Created >= filter.CreatedFrom.Value); + + if (filter.CreatedTo.HasValue) + query = query.Where(x => x.Created <= filter.CreatedTo.Value); + + return query; + } +} diff --git a/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetPaymentTransactionSummary/GetPaymentTransactionSummaryQuery.cs b/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetPaymentTransactionSummary/GetPaymentTransactionSummaryQuery.cs new file mode 100644 index 0000000..6abd4e5 --- /dev/null +++ b/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetPaymentTransactionSummary/GetPaymentTransactionSummaryQuery.cs @@ -0,0 +1,14 @@ +namespace CMSMicroservice.Application.PaymentTransactionCQ.Queries.GetPaymentTransactionSummary; + +public record GetPaymentTransactionSummaryQuery : IRequest +{ + public GetAllPaymentTransactionByFilter.GetAllPaymentTransactionByFilterFilter? Filter { get; init; } +} + +public class GetPaymentTransactionSummaryResponseDto +{ + public long TotalCount { get; set; } + public long TotalAmount { get; set; } + public long SuccessfulCount { get; set; } + public long PendingCount { get; set; } +} diff --git a/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetPaymentTransactionSummary/GetPaymentTransactionSummaryQueryHandler.cs b/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetPaymentTransactionSummary/GetPaymentTransactionSummaryQueryHandler.cs new file mode 100644 index 0000000..218d845 --- /dev/null +++ b/src/CMSMicroservice.Application/PaymentTransactionCQ/Queries/GetPaymentTransactionSummary/GetPaymentTransactionSummaryQueryHandler.cs @@ -0,0 +1,36 @@ +using CMSMicroservice.Application.PaymentTransactionCQ.Queries.GetAllPaymentTransactionByFilter; + +namespace CMSMicroservice.Application.PaymentTransactionCQ.Queries.GetPaymentTransactionSummary; + +public class GetPaymentTransactionSummaryQueryHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetPaymentTransactionSummaryQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetPaymentTransactionSummaryQuery request, + CancellationToken cancellationToken) + { + var query = _context.PaymentTransactions + .AsNoTracking() + .ApplyPaymentTransactionFilter(request.Filter); + + var totalCount = await query.LongCountAsync(cancellationToken); + var totalAmount = totalCount > 0 ? await query.SumAsync(x => x.Amount, cancellationToken) : 0; + var successfulCount = await query.LongCountAsync(x => x.PaymentStatus, cancellationToken); + var pendingCount = await query.LongCountAsync(x => !x.PaymentStatus, cancellationToken); + + return new GetPaymentTransactionSummaryResponseDto + { + TotalCount = totalCount, + TotalAmount = totalAmount, + SuccessfulCount = successfulCount, + PendingCount = pendingCount + }; + } +} diff --git a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetAllTransactionsByFilter/GetAllTransactionsByFilterQuery.cs b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetAllTransactionsByFilter/GetAllTransactionsByFilterQuery.cs index 2601756..f8f647f 100644 --- a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetAllTransactionsByFilter/GetAllTransactionsByFilterQuery.cs +++ b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetAllTransactionsByFilter/GetAllTransactionsByFilterQuery.cs @@ -26,4 +26,8 @@ public record GetAllTransactionsByFilterQuery : IRequest { private readonly IApplicationDbContext _context; @@ -11,26 +12,27 @@ public class GetAllTransactionsByFilterQueryHandler : IRequestHandler Handle(GetAllTransactionsByFilterQuery request, CancellationToken cancellationToken) { var query = _context.Transactions - .ApplyOrder(sortBy: request.SortBy) .AsNoTracking() - .AsQueryable(); - if (request.Filter is not null) - { - query = query - .Where(x => request.Filter.Id == null || x.Id == request.Filter.Id) - .Where(x => request.Filter.Amount == null || x.Amount == request.Filter.Amount) - .Where(x => request.Filter.Description == null || x.Description.Contains(request.Filter.Description)) - .Where(x => request.Filter.PaymentStatus == null || x.PaymentStatus== request.Filter.PaymentStatus) - .Where(x => request.Filter.PaymentDate == null || x.PaymentDate == request.Filter.PaymentDate) - .Where(x => request.Filter.RefId == null || x.RefId.Contains(request.Filter.RefId)) - .Where(x => request.Filter.Type == null || x.Type == request.Filter.Type) -; - } + .ApplyTransactionFilter(request.Filter) + .ApplyOrder(sortBy: request.SortBy ?? "-Created"); + return new GetAllTransactionsByFilterResponseDto { MetaData = await query.GetMetaData(request.PaginationState, cancellationToken), - Models = await query.PaginatedListAsync(paginationState: request.PaginationState) - .ProjectToType().ToListAsync(cancellationToken) - }; + Models = await query + .PaginatedListAsync(paginationState: request.PaginationState) + .Select(x => new GetAllTransactionsByFilterResponseModel + { + Id = x.Id, + Amount = x.Amount, + Description = x.Description, + PaymentStatus = x.PaymentStatus, + PaymentDate = x.PaymentDate, + RefId = x.RefId, + Type = x.Type, + Created = x.Created + }) + .ToListAsync(cancellationToken) + }; } } diff --git a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetAllTransactionsByFilter/GetAllTransactionsByFilterResponseDto.cs b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetAllTransactionsByFilter/GetAllTransactionsByFilterResponseDto.cs index 6e3937d..ed4e290 100644 --- a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetAllTransactionsByFilter/GetAllTransactionsByFilterResponseDto.cs +++ b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetAllTransactionsByFilter/GetAllTransactionsByFilterResponseDto.cs @@ -24,4 +24,5 @@ public class GetAllTransactionsByFilterResponseDto public string? RefId { get; set; } // public TransactionType Type { get; set; } + public DateTime Created { get; set; } } diff --git a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetAllTransactionsByFilter/TransactionFilterExtensions.cs b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetAllTransactionsByFilter/TransactionFilterExtensions.cs new file mode 100644 index 0000000..06dd97f --- /dev/null +++ b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetAllTransactionsByFilter/TransactionFilterExtensions.cs @@ -0,0 +1,48 @@ +using CMSMicroservice.Domain.Entities; + +namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetAllTransactionsByFilter; + +public static class TransactionFilterExtensions +{ + public static IQueryable ApplyTransactionFilter( + this IQueryable query, + GetAllTransactionsByFilterFilter? filter) + { + if (filter == null) return query; + + if (filter.Id.HasValue) + query = query.Where(x => x.Id == filter.Id.Value); + + if (filter.Amount.HasValue) + query = query.Where(x => x.Amount == filter.Amount.Value); + + if (!string.IsNullOrWhiteSpace(filter.Description)) + query = query.Where(x => x.Description.Contains(filter.Description)); + + if (filter.PaymentStatus.HasValue) + query = query.Where(x => x.PaymentStatus == filter.PaymentStatus.Value); + + if (filter.PaymentDate.HasValue) + query = query.Where(x => x.PaymentDate == filter.PaymentDate.Value); + + if (!string.IsNullOrWhiteSpace(filter.RefId)) + query = query.Where(x => x.RefId != null && x.RefId.Contains(filter.RefId)); + + if (filter.Type.HasValue) + query = query.Where(x => x.Type == filter.Type.Value); + + if (filter.CreatedFrom.HasValue) + query = query.Where(x => x.Created >= filter.CreatedFrom.Value); + + if (filter.CreatedTo.HasValue) + query = query.Where(x => x.Created <= filter.CreatedTo.Value); + + if (filter.PaymentDateFrom.HasValue) + query = query.Where(x => x.PaymentDate >= filter.PaymentDateFrom.Value); + + if (filter.PaymentDateTo.HasValue) + query = query.Where(x => x.PaymentDate <= filter.PaymentDateTo.Value); + + return query; + } +} diff --git a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetTransactionSummary/GetTransactionSummaryQuery.cs b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetTransactionSummary/GetTransactionSummaryQuery.cs new file mode 100644 index 0000000..05c24bf --- /dev/null +++ b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetTransactionSummary/GetTransactionSummaryQuery.cs @@ -0,0 +1,17 @@ +using CMSMicroservice.Application.TransactionsCQ.Queries.GetAllTransactionsByFilter; + +namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetTransactionSummary; + +public record GetTransactionSummaryQuery : IRequest +{ + public GetAllTransactionsByFilterFilter? Filter { get; init; } +} + +public class GetTransactionSummaryResponseDto +{ + public long TotalCount { get; set; } + public long TotalAmount { get; set; } + public long SuccessCount { get; set; } + public long PendingCount { get; set; } + public long RejectCount { get; set; } +} diff --git a/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetTransactionSummary/GetTransactionSummaryQueryHandler.cs b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetTransactionSummary/GetTransactionSummaryQueryHandler.cs new file mode 100644 index 0000000..08bc11f --- /dev/null +++ b/src/CMSMicroservice.Application/TransactionsCQ/Queries/GetTransactionSummary/GetTransactionSummaryQueryHandler.cs @@ -0,0 +1,39 @@ +using CMSMicroservice.Application.TransactionsCQ.Queries.GetAllTransactionsByFilter; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetTransactionSummary; + +public class GetTransactionSummaryQueryHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetTransactionSummaryQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetTransactionSummaryQuery request, + CancellationToken cancellationToken) + { + var query = _context.Transactions + .AsNoTracking() + .ApplyTransactionFilter(request.Filter); + + var totalCount = await query.LongCountAsync(cancellationToken); + var totalAmount = totalCount > 0 ? await query.SumAsync(x => x.Amount, cancellationToken) : 0; + var successCount = await query.LongCountAsync(x => x.PaymentStatus == PaymentStatus.Success, cancellationToken); + var pendingCount = await query.LongCountAsync(x => x.PaymentStatus == PaymentStatus.Pending, cancellationToken); + var rejectCount = await query.LongCountAsync(x => x.PaymentStatus == PaymentStatus.Reject, cancellationToken); + + return new GetTransactionSummaryResponseDto + { + TotalCount = totalCount, + TotalAmount = totalAmount, + SuccessCount = successCount, + PendingCount = pendingCount, + RejectCount = rejectCount + }; + } +} diff --git a/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetAllUserPackagePurchaseByFilter/GetAllUserPackagePurchaseByFilterQuery.cs b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetAllUserPackagePurchaseByFilter/GetAllUserPackagePurchaseByFilterQuery.cs new file mode 100644 index 0000000..5fe581a --- /dev/null +++ b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetAllUserPackagePurchaseByFilter/GetAllUserPackagePurchaseByFilterQuery.cs @@ -0,0 +1,43 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter; + +public record GetAllUserPackagePurchaseByFilterQuery : IRequest +{ + public PaginationState? PaginationState { get; init; } + public string? SortBy { get; init; } + public GetAllUserPackagePurchaseByFilterFilter? Filter { get; init; } +} + +public class GetAllUserPackagePurchaseByFilterFilter +{ + public long? UserId { get; set; } + public long? PackageId { get; set; } + public PackagePurchaseMethod? PurchaseMethod { get; set; } + public DateTime? PurchasedFrom { get; set; } + public DateTime? PurchasedTo { get; set; } + public long? MinAmount { get; set; } + public long? MaxAmount { get; set; } +} + +public class GetAllUserPackagePurchaseByFilterResponseDto +{ + public MetaData MetaData { get; set; } + public List? Models { get; set; } +} + +public class GetAllUserPackagePurchaseByFilterResponseModel +{ + public long Id { get; set; } + public long UserId { get; set; } + public string UserName { get; set; } = string.Empty; + public string UserMobile { get; set; } = string.Empty; + public long PackageId { get; set; } + public string PackageName { get; set; } = string.Empty; + public PackagePurchaseMethod PurchaseMethod { get; set; } + public DateTime PurchasedAt { get; set; } + public long Amount { get; set; } + public long? OrderId { get; set; } + public long? TransactionId { get; set; } + public DateTimeOffset Created { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetAllUserPackagePurchaseByFilter/GetAllUserPackagePurchaseByFilterQueryHandler.cs b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetAllUserPackagePurchaseByFilter/GetAllUserPackagePurchaseByFilterQueryHandler.cs new file mode 100644 index 0000000..bfdcdfc --- /dev/null +++ b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetAllUserPackagePurchaseByFilter/GetAllUserPackagePurchaseByFilterQueryHandler.cs @@ -0,0 +1,47 @@ +namespace CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter; + +public class GetAllUserPackagePurchaseByFilterQueryHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllUserPackagePurchaseByFilterQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetAllUserPackagePurchaseByFilterQuery request, + CancellationToken cancellationToken) + { + var query = _context.UserPackagePurchases + .Include(x => x.User) + .Include(x => x.Package) + .AsNoTracking() + .ApplyUserPackagePurchaseFilter(request.Filter) + .ApplyOrder(sortBy: request.SortBy ?? "-PurchasedAt"); + + return new GetAllUserPackagePurchaseByFilterResponseDto + { + MetaData = await query.GetMetaData(request.PaginationState, cancellationToken), + Models = await query + .PaginatedListAsync(paginationState: request.PaginationState) + .Select(x => new GetAllUserPackagePurchaseByFilterResponseModel + { + Id = x.Id, + UserId = x.UserId, + UserName = x.User != null ? $"{x.User.FirstName} {x.User.LastName}" : string.Empty, + UserMobile = x.User != null ? x.User.Mobile : string.Empty, + PackageId = x.PackageId, + PackageName = x.Package != null ? x.Package.Title : string.Empty, + PurchaseMethod = x.PurchaseMethod, + PurchasedAt = x.PurchasedAt, + Amount = x.Amount, + OrderId = x.OrderId, + TransactionId = x.TransactionId, + Created = x.Created + }) + .ToListAsync(cancellationToken) + }; + } +} diff --git a/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetAllUserPackagePurchaseByFilter/UserPackagePurchaseFilterExtensions.cs b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetAllUserPackagePurchaseByFilter/UserPackagePurchaseFilterExtensions.cs new file mode 100644 index 0000000..62aa625 --- /dev/null +++ b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetAllUserPackagePurchaseByFilter/UserPackagePurchaseFilterExtensions.cs @@ -0,0 +1,36 @@ +using CMSMicroservice.Domain.Entities; + +namespace CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter; + +public static class UserPackagePurchaseFilterExtensions +{ + public static IQueryable ApplyUserPackagePurchaseFilter( + this IQueryable query, + GetAllUserPackagePurchaseByFilterFilter? filter) + { + if (filter == null) return query; + + if (filter.UserId.HasValue) + query = query.Where(x => x.UserId == filter.UserId.Value); + + if (filter.PackageId.HasValue) + query = query.Where(x => x.PackageId == filter.PackageId.Value); + + if (filter.PurchaseMethod.HasValue) + query = query.Where(x => x.PurchaseMethod == filter.PurchaseMethod.Value); + + if (filter.PurchasedFrom.HasValue) + query = query.Where(x => x.PurchasedAt >= filter.PurchasedFrom.Value); + + if (filter.PurchasedTo.HasValue) + query = query.Where(x => x.PurchasedAt <= filter.PurchasedTo.Value); + + if (filter.MinAmount.HasValue) + query = query.Where(x => x.Amount >= filter.MinAmount.Value); + + if (filter.MaxAmount.HasValue) + query = query.Where(x => x.Amount <= filter.MaxAmount.Value); + + return query; + } +} diff --git a/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetUserPackagePurchaseSummary/GetUserPackagePurchaseSummaryQuery.cs b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetUserPackagePurchaseSummary/GetUserPackagePurchaseSummaryQuery.cs new file mode 100644 index 0000000..abfe52b --- /dev/null +++ b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetUserPackagePurchaseSummary/GetUserPackagePurchaseSummaryQuery.cs @@ -0,0 +1,12 @@ +namespace CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetUserPackagePurchaseSummary; + +public record GetUserPackagePurchaseSummaryQuery : IRequest +{ + public GetAllUserPackagePurchaseByFilter.GetAllUserPackagePurchaseByFilterFilter? Filter { get; init; } +} + +public class GetUserPackagePurchaseSummaryResponseDto +{ + public long TotalCount { get; set; } + public long TotalAmount { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetUserPackagePurchaseSummary/GetUserPackagePurchaseSummaryQueryHandler.cs b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetUserPackagePurchaseSummary/GetUserPackagePurchaseSummaryQueryHandler.cs new file mode 100644 index 0000000..654cb75 --- /dev/null +++ b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetUserPackagePurchaseSummary/GetUserPackagePurchaseSummaryQueryHandler.cs @@ -0,0 +1,32 @@ +using CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter; + +namespace CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetUserPackagePurchaseSummary; + +public class GetUserPackagePurchaseSummaryQueryHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetUserPackagePurchaseSummaryQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetUserPackagePurchaseSummaryQuery request, + CancellationToken cancellationToken) + { + var query = _context.UserPackagePurchases + .AsNoTracking() + .ApplyUserPackagePurchaseFilter(request.Filter); + + var totalCount = await query.LongCountAsync(cancellationToken); + var totalAmount = totalCount > 0 ? await query.SumAsync(x => x.Amount, cancellationToken) : 0; + + return new GetUserPackagePurchaseSummaryResponseDto + { + TotalCount = totalCount, + TotalAmount = totalAmount + }; + } +} diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeCreditWallet/ChargeCreditWalletCommand.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeCreditWallet/ChargeCreditWalletCommand.cs new file mode 100644 index 0000000..fc6dab9 --- /dev/null +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeCreditWallet/ChargeCreditWalletCommand.cs @@ -0,0 +1,13 @@ +using CMSMicroservice.Application.Common.Models; +using MediatR; + +namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeCreditWallet; + +/// +/// دستور شارژ کیف پول اصلی از طریق درگاه +/// +public class ChargeCreditWalletCommand : IRequest +{ + public long UserId { get; set; } + public long Amount { get; set; } +} diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeCreditWallet/ChargeCreditWalletCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeCreditWallet/ChargeCreditWalletCommandHandler.cs new file mode 100644 index 0000000..d983cb7 --- /dev/null +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeCreditWallet/ChargeCreditWalletCommandHandler.cs @@ -0,0 +1,123 @@ +using CMSMicroservice.Application.Common; +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Entities.Payment; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeCreditWallet; + +public class ChargeCreditWalletCommandHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IPaymentGatewayService _paymentGateway; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private readonly IUserPaymentLock _paymentLock; + + public ChargeCreditWalletCommandHandler( + IApplicationDbContext context, + IPaymentGatewayService paymentGateway, + IConfiguration configuration, + ILogger logger, + IUserPaymentLock paymentLock) + { + _context = context; + _paymentGateway = paymentGateway; + _configuration = configuration; + _logger = logger; + _paymentLock = paymentLock; + } + + public Task Handle( + ChargeCreditWalletCommand request, + CancellationToken cancellationToken) => + _paymentLock.ExecuteAsync( + PaymentLockScopes.Initiate(request.UserId), + PaymentLockStrategy.FailFast, + ct => HandleCore(request, ct), + cancellationToken); + + private async Task HandleCore( + ChargeCreditWalletCommand request, + CancellationToken cancellationToken) + { + try + { + _logger.LogInformation( + "Charging credit wallet for UserId: {UserId}, Amount: {Amount}", + request.UserId, + request.Amount); + + var user = await _context.Users + .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken) + ?? throw new NotFoundException(nameof(User), request.UserId); + + var wallet = await _context.UserWallets + .FirstOrDefaultAsync(w => w.UserId == request.UserId, cancellationToken) + ?? throw new NotFoundException("کیف پول کاربر یافت نشد"); + + var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; + var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback?type=credit-wallet"; + + var paymentRequest = new PaymentRequest + { + Amount = request.Amount, + UserId = user.Id, + Mobile = user.Mobile ?? "", + CallbackUrl = callbackUrl, + Description = $"شارژ کیف پول اصلی - کاربر {user.Id}" + }; + + var paymentResult = await _paymentGateway.InitiatePaymentAsync(paymentRequest); + + if (!paymentResult.IsSuccess) + { + _logger.LogError( + "Payment gateway failed for UserId {UserId}: {ErrorMessage}", + user.Id, + paymentResult.ErrorMessage); + + throw new Exception($"خطا در ارتباط با درگاه پرداخت: {paymentResult.ErrorMessage}"); + } + + var paymentTx = new PaymentTransaction + { + GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal", + MerchantId = _configuration["ZarinPal:MerchantId"] ?? "", + Amount = request.Amount, + CallbackUrl = callbackUrl, + Description = $"شارژ کیف پول اصلی - کاربر {user.Id}", + Mobile = user.Mobile, + UserId = user.Id, + RequestStatusCode = 100, + RequestStatusMessage = "Success", + Authority = paymentResult.RefId, + PaymentStatus = false + }; + _context.PaymentTransactions.Add(paymentTx); + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation( + "Credit wallet charge initiated. UserId: {UserId}, RefId: {RefId}, PaymentTxId: {PaymentTxId}", + user.Id, + paymentResult.RefId, + paymentTx.Id); + + return paymentResult; + } + catch (Exception ex) + { + _logger.LogError( + ex, + "Error in ChargeCreditWalletCommand for UserId: {UserId}", + request.UserId); + throw; + } + } +} diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeCreditWallet/ChargeCreditWalletCommandValidator.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeCreditWallet/ChargeCreditWalletCommandValidator.cs new file mode 100644 index 0000000..8e106b7 --- /dev/null +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/ChargeCreditWallet/ChargeCreditWalletCommandValidator.cs @@ -0,0 +1,20 @@ +using CMSMicroservice.Domain.Common; +using FluentValidation; + +namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeCreditWallet; + +public class ChargeCreditWalletCommandValidator : AbstractValidator +{ + public ChargeCreditWalletCommandValidator() + { + RuleFor(x => x.UserId) + .GreaterThan(0) + .WithMessage("شناسه کاربر باید بزرگتر از صفر باشد"); + + RuleFor(x => x.Amount) + .GreaterThanOrEqualTo(SystemConstants.DiscountWalletMinCharge) + .WithMessage("حداقل مبلغ شارژ ۱۰,۰۰۰ تومان است") + .LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount) + .WithMessage("مبلغ وارد شده بیش از حد مجاز است"); + } +} diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyCreditWalletCharge/VerifyCreditWalletChargeCommand.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyCreditWalletCharge/VerifyCreditWalletChargeCommand.cs new file mode 100644 index 0000000..4ec5aad --- /dev/null +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyCreditWalletCharge/VerifyCreditWalletChargeCommand.cs @@ -0,0 +1,13 @@ +using MediatR; + +namespace CMSMicroservice.Application.WalletCQ.Commands.VerifyCreditWalletCharge; + +/// +/// دستور تأیید شارژ کیف پول اصلی +/// +public class VerifyCreditWalletChargeCommand : IRequest +{ + public long UserId { get; set; } + public long Amount { get; set; } + public string Authority { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyCreditWalletCharge/VerifyCreditWalletChargeCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyCreditWalletCharge/VerifyCreditWalletChargeCommandHandler.cs new file mode 100644 index 0000000..c3985a5 --- /dev/null +++ b/src/CMSMicroservice.Application/WalletCQ/Commands/VerifyCreditWalletCharge/VerifyCreditWalletChargeCommandHandler.cs @@ -0,0 +1,157 @@ +using CMSMicroservice.Application.Common; +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.WalletCQ.Commands.VerifyCreditWalletCharge; + +public class VerifyCreditWalletChargeCommandHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IPaymentGatewayService _paymentGateway; + private readonly ILogger _logger; + private readonly IUserPaymentLock _paymentLock; + + public VerifyCreditWalletChargeCommandHandler( + IApplicationDbContext context, + IPaymentGatewayService paymentGateway, + ILogger logger, + IUserPaymentLock paymentLock) + { + _context = context; + _paymentGateway = paymentGateway; + _logger = logger; + _paymentLock = paymentLock; + } + + public Task Handle( + VerifyCreditWalletChargeCommand request, + CancellationToken cancellationToken) => + _paymentLock.ExecuteAsync( + PaymentLockScopes.Verify(request.UserId, request.Authority), + PaymentLockStrategy.WaitForRelease, + ct => HandleCore(request, ct), + cancellationToken); + + private async Task HandleCore( + VerifyCreditWalletChargeCommand request, + CancellationToken cancellationToken) + { + try + { + _logger.LogInformation( + "Verifying credit wallet charge. UserId: {UserId}, Amount: {Amount}, Authority: {Authority}", + request.UserId, + request.Amount, + request.Authority); + + var user = await _context.Users + .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken) + ?? throw new NotFoundException(nameof(User), request.UserId); + + var paymentTx = await _context.PaymentTransactions + .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, cancellationToken); + + if (paymentTx?.PaymentStatus == true) + { + _logger.LogWarning("PaymentTransaction already verified: {Authority}", request.Authority); + return true; + } + + var amountInToman = (decimal)(paymentTx?.Amount ?? 0); + + var verifyResult = await _paymentGateway.VerifyPaymentAsync( + request.Authority, + "OK", + amountInToman, + cancellationToken); + + if (paymentTx != null) + { + paymentTx.PaymentStatus = verifyResult.IsSuccess; + paymentTx.VerificationStatusCode = verifyResult.VerificationCode; + paymentTx.VerificationStatusMessage = verifyResult.Message; + paymentTx.CardPan = verifyResult.CardPan; + paymentTx.CardHash = verifyResult.CardHash; + paymentTx.RefId = verifyResult.TrackingCode; + } + + if (!verifyResult.IsSuccess) + { + _logger.LogWarning( + "Credit wallet charge verification failed for UserId {UserId}: {Message}", + request.UserId, + verifyResult.Message); + + throw new Exception($"تراکنش ناموفق: {verifyResult.Message}"); + } + + var wallet = await _context.UserWallets + .FirstOrDefaultAsync(w => w.UserId == user.Id, cancellationToken) + ?? throw new NotFoundException($"کیف پول کاربر با شناسه {request.UserId} یافت نشد"); + + var oldBalance = wallet.Balance; + wallet.Balance += request.Amount; + + _logger.LogInformation( + "Charging credit balance for UserId {UserId}: {OldBalance} -> {NewBalance}", + request.UserId, + oldBalance, + wallet.Balance); + + var transaction = new Transaction + { + Amount = request.Amount, + Description = $"شارژ کیف پول اصلی - کاربر {user.Id}", + PaymentStatus = PaymentStatus.Success, + PaymentDate = DateTime.Now, + RefId = verifyResult.RefId, + Type = TransactionType.CreditWalletCharge + }; + + _context.Transactions.Add(transaction); + await _context.SaveChangesAsync(cancellationToken); + + _context.UserWalletHistories.Add(new Domain.Entities.UserWalletHistory + { + WalletId = wallet.Id, + CurrentBalance = wallet.Balance, + ChangeValue = request.Amount, + CurrentNetworkBalance = wallet.NetworkBalance, + ChangeNerworkValue = 0, + CurrentDiscountBalance = wallet.DiscountBalance, + ChangeDiscountValue = 0, + IsIncrease = true, + RefrenceId = transaction.Id + }); + await _context.SaveChangesAsync(cancellationToken); + + if (paymentTx != null) + { + paymentTx.TransactionId = transaction.Id; + await _context.SaveChangesAsync(cancellationToken); + } + + _logger.LogInformation( + "Credit wallet charged successfully. UserId: {UserId}, TransactionId: {TransactionId}, RefId: {RefId}", + user.Id, + transaction.Id, + verifyResult.RefId); + + return true; + } + catch (Exception ex) + { + _logger.LogError( + ex, + "Error in VerifyCreditWalletChargeCommand for UserId: {UserId}", + request.UserId); + throw; + } + } +} diff --git a/src/CMSMicroservice.Domain/Enums/TransactionType.cs b/src/CMSMicroservice.Domain/Enums/TransactionType.cs index f93f9b6..7b32d15 100644 --- a/src/CMSMicroservice.Domain/Enums/TransactionType.cs +++ b/src/CMSMicroservice.Domain/Enums/TransactionType.cs @@ -36,4 +36,9 @@ public enum TransactionType /// بونوس داخلی کیف‌پول جادویی (مبلغ × 1.5) /// MagicWalletBonus = 15, + + /// + /// شارژ کیف پول اصلی از درگاه + /// + CreditWalletCharge = 16, } diff --git a/src/CMSMicroservice.Infrastructure/BackgroundJobs/ZarinpalReconciliationJob.cs b/src/CMSMicroservice.Infrastructure/BackgroundJobs/ZarinpalReconciliationJob.cs index 4e402f7..0e39c69 100644 --- a/src/CMSMicroservice.Infrastructure/BackgroundJobs/ZarinpalReconciliationJob.cs +++ b/src/CMSMicroservice.Infrastructure/BackgroundJobs/ZarinpalReconciliationJob.cs @@ -3,6 +3,7 @@ using CMSMicroservice.Application.PackageCQ.Commands.VerifyUserPackagePurchasePa using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment; using CMSMicroservice.Application.WalletCQ.Commands.VerifyDiscountWalletCharge; +using CMSMicroservice.Application.WalletCQ.Commands.VerifyCreditWalletCharge; using CMSMicroservice.Application.WalletCQ.Commands.VerifyMagicWalletCharge; using CMSMicroservice.Domain.Entities; using CMSMicroservice.Domain.Entities.Payment; @@ -174,6 +175,19 @@ public class ZarinpalReconciliationJob }, ct); break; + case "credit-wallet": + if (!paymentTx.UserId.HasValue) + throw new InvalidOperationException( + $"UserId is null for credit-wallet authority {authority}"); + + await _sender.Send(new VerifyCreditWalletChargeCommand + { + Authority = authority, + UserId = paymentTx.UserId.Value, + Amount = paymentTx.Amount + }, ct); + break; + case "discount-order": await ReconcileDiscountOrderAsync(paymentTx, ct); break; @@ -194,6 +208,8 @@ public class ZarinpalReconciliationJob return "magic-wallet"; if (callbackUrl.Contains("type=discount-wallet", StringComparison.OrdinalIgnoreCase)) return "discount-wallet"; + if (callbackUrl.Contains("type=credit-wallet", StringComparison.OrdinalIgnoreCase)) + return "credit-wallet"; if (callbackUrl.Contains("type=discount-order", StringComparison.OrdinalIgnoreCase)) return "discount-order"; // Package callbacks have orderId= but no type= parameter diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 8525e81..911d4c6 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.200 + 0.0.203 None False False @@ -74,6 +74,11 @@ + + + + + diff --git a/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto b/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto index 9520d8d..eb5957e 100644 --- a/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto +++ b/src/CMSMicroservice.Protobuf/Protos/clubmembership.proto @@ -164,11 +164,19 @@ message ClubMembershipModel // GetHistory Query message GetClubMembershipHistoryRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetClubMembershipHistoryFilter filter = 3; +} + +message GetClubMembershipHistoryFilter { google.protobuf.Int64Value user_id = 1; - google.protobuf.Int64Value package_id = 2; - int32 page_index = 3; - int32 page_size = 4; + google.protobuf.Int64Value club_membership_id = 2; + google.protobuf.Int32Value action = 3; + google.protobuf.Timestamp created_from = 4; + google.protobuf.Timestamp created_to = 5; } message GetClubMembershipHistoryResponse @@ -182,16 +190,15 @@ message ClubMembershipHistoryModel int64 id = 1; int64 club_membership_id = 2; int64 user_id = 3; - google.protobuf.Int64Value old_package_id = 4; - google.protobuf.Int64Value new_package_id = 5; - google.protobuf.Timestamp old_activated_at = 6; - google.protobuf.Timestamp new_activated_at = 7; - google.protobuf.Timestamp old_expires_at = 8; - google.protobuf.Timestamp new_expires_at = 9; - int32 action = 10; // ClubMembershipAction enum - string performed_by = 11; - string reason = 12; - google.protobuf.Timestamp created = 13; + string user_name = 4; + bool old_is_active = 5; + bool new_is_active = 6; + google.protobuf.Int64Value old_initial_contribution = 7; + google.protobuf.Int64Value new_initial_contribution = 8; + int32 action = 9; // ClubMembershipAction enum + string performed_by = 10; + string reason = 11; + google.protobuf.Timestamp created_at = 12; } // GetClubStatistics Query diff --git a/src/CMSMicroservice.Protobuf/Protos/clubmembershipcycle.proto b/src/CMSMicroservice.Protobuf/Protos/clubmembershipcycle.proto new file mode 100644 index 0000000..e68ecf7 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/clubmembershipcycle.proto @@ -0,0 +1,79 @@ +syntax = "proto3"; + +package clubmembershipcycle; + +import "public_messages.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.ClubMembershipCycle"; + +service ClubMembershipCycleContract +{ + rpc GetAllClubMembershipCycleByFilter(GetAllClubMembershipCycleByFilterRequest) returns (GetAllClubMembershipCycleByFilterResponse){ + option (google.api.http) = { + get: "/ClubMembershipCycle/GetAllByFilter" + }; + }; + rpc GetClubMembershipCycleSummary(GetClubMembershipCycleSummaryRequest) returns (GetClubMembershipCycleSummaryResponse){ + option (google.api.http) = { + get: "/ClubMembershipCycle/GetSummary" + }; + }; +} + +message GetAllClubMembershipCycleByFilterRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllClubMembershipCycleByFilterFilter filter = 3; +} + +message GetAllClubMembershipCycleByFilterFilter +{ + google.protobuf.Int64Value user_id = 1; + google.protobuf.BoolValue is_current_cycle = 2; + google.protobuf.Int64Value package_id = 3; + google.protobuf.Int32Value cycle_number = 4; + google.protobuf.Int32Value purchase_method = 5; + google.protobuf.Int32Value magic_state = 6; // 0=none, 1=active, 2=completed + google.protobuf.Timestamp package_purchased_from = 7; + google.protobuf.Timestamp package_purchased_to = 8; +} + +message GetAllClubMembershipCycleByFilterResponse +{ + messages.MetaData meta_data = 1; + repeated ClubMembershipCycleModel models = 2; +} + +message ClubMembershipCycleModel +{ + int64 id = 1; + int64 user_id = 2; + string user_name = 3; + int64 club_membership_id = 4; + int32 cycle_number = 5; + int64 package_id = 6; + string package_name = 7; + int64 package_amount = 8; + int32 purchase_method = 9; + google.protobuf.Timestamp package_purchased_at = 10; + google.protobuf.Timestamp magic_started_at = 11; + google.protobuf.Timestamp magic_completed_at = 12; + bool is_current_cycle = 13; + google.protobuf.Timestamp created_at = 14; +} + +message GetClubMembershipCycleSummaryRequest +{ + GetAllClubMembershipCycleByFilterFilter filter = 1; +} + +message GetClubMembershipCycleSummaryResponse +{ + int64 total_count = 1; + int64 current_cycle_count = 2; + int64 magic_active_count = 3; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/clubmembershipcyclehistory.proto b/src/CMSMicroservice.Protobuf/Protos/clubmembershipcyclehistory.proto new file mode 100644 index 0000000..44fe61f --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/clubmembershipcyclehistory.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package clubmembershipcyclehistory; + +import "public_messages.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.ClubMembershipCycleHistory"; + +service ClubMembershipCycleHistoryContract +{ + rpc GetAllClubMembershipCycleHistoryByFilter(GetAllClubMembershipCycleHistoryByFilterRequest) returns (GetAllClubMembershipCycleHistoryByFilterResponse){ + option (google.api.http) = { + get: "/ClubMembershipCycleHistory/GetAllByFilter" + }; + }; +} + +message GetAllClubMembershipCycleHistoryByFilterRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllClubMembershipCycleHistoryByFilterFilter filter = 3; +} + +message GetAllClubMembershipCycleHistoryByFilterFilter +{ + google.protobuf.Int64Value user_id = 1; + google.protobuf.Int64Value club_membership_cycle_id = 2; + google.protobuf.Int32Value cycle_number = 3; + google.protobuf.Int32Value action = 4; + google.protobuf.Timestamp created_from = 5; + google.protobuf.Timestamp created_to = 6; +} + +message GetAllClubMembershipCycleHistoryByFilterResponse +{ + messages.MetaData meta_data = 1; + repeated ClubMembershipCycleHistoryModel models = 2; +} + +message ClubMembershipCycleHistoryModel +{ + int64 id = 1; + int64 club_membership_cycle_id = 2; + int64 user_id = 3; + string user_name = 4; + int32 cycle_number = 5; + bool old_is_current_cycle = 6; + bool new_is_current_cycle = 7; + google.protobuf.Timestamp old_magic_started_at = 8; + google.protobuf.Timestamp new_magic_started_at = 9; + google.protobuf.Timestamp old_magic_completed_at = 10; + google.protobuf.Timestamp new_magic_completed_at = 11; + int32 action = 12; + string performed_by = 13; + string reason = 14; + google.protobuf.Timestamp created_at = 15; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/paymenttransaction.proto b/src/CMSMicroservice.Protobuf/Protos/paymenttransaction.proto new file mode 100644 index 0000000..488c6b6 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/paymenttransaction.proto @@ -0,0 +1,82 @@ +syntax = "proto3"; + +package paymenttransaction; + +import "public_messages.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.PaymentTransaction"; + +service PaymentTransactionContract +{ + rpc GetAllPaymentTransactionByFilter(GetAllPaymentTransactionByFilterRequest) returns (GetAllPaymentTransactionByFilterResponse){ + option (google.api.http) = { + get: "/PaymentTransaction/GetAllByFilter" + }; + }; + rpc GetPaymentTransactionSummary(GetPaymentTransactionSummaryRequest) returns (GetPaymentTransactionSummaryResponse){ + option (google.api.http) = { + get: "/PaymentTransaction/GetSummary" + }; + }; +} + +message GetAllPaymentTransactionByFilterRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllPaymentTransactionByFilterFilter filter = 3; +} + +message GetAllPaymentTransactionByFilterFilter +{ + google.protobuf.Int64Value user_id = 1; + google.protobuf.StringValue mobile = 2; + google.protobuf.StringValue gateway_provider = 3; + google.protobuf.BoolValue payment_status = 4; + google.protobuf.StringValue authority = 5; + google.protobuf.StringValue ref_id = 6; + google.protobuf.StringValue order_id = 7; + google.protobuf.Timestamp created_from = 8; + google.protobuf.Timestamp created_to = 9; +} + +message GetAllPaymentTransactionByFilterResponse +{ + messages.MetaData meta_data = 1; + repeated PaymentTransactionModel models = 2; +} + +message PaymentTransactionModel +{ + int64 id = 1; + int64 user_id = 2; + string mobile = 3; + string gateway_provider = 4; + int64 amount = 5; + string authority = 6; + bool payment_status = 7; + google.protobuf.Int32Value verification_status_code = 8; + string verification_status_message = 9; + string ref_id = 10; + string card_pan = 11; + string order_id = 12; + google.protobuf.Int64Value transaction_id = 13; + string description = 14; + google.protobuf.Timestamp created_at = 15; +} + +message GetPaymentTransactionSummaryRequest +{ + GetAllPaymentTransactionByFilterFilter filter = 1; +} + +message GetPaymentTransactionSummaryResponse +{ + int64 total_count = 1; + int64 total_amount = 2; + int64 successful_count = 3; + int64 pending_count = 4; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/public_messages.proto b/src/CMSMicroservice.Protobuf/Protos/public_messages.proto index 095918c..a3c55c6 100644 --- a/src/CMSMicroservice.Protobuf/Protos/public_messages.proto +++ b/src/CMSMicroservice.Protobuf/Protos/public_messages.proto @@ -109,6 +109,13 @@ enum TransactionType DepositIpg = 1; DepositExternal1 = 2; Withdraw = 3; + NetworkCommission = 10; + ClubActivation = 11; + DiscountWalletCharge = 12; + DiscountShopPurchase = 13; + MagicWalletDeposit = 14; + MagicWalletBonus = 15; + CreditWalletCharge = 16; } enum ContractType { diff --git a/src/CMSMicroservice.Protobuf/Protos/transactions.proto b/src/CMSMicroservice.Protobuf/Protos/transactions.proto index 2255bac..07a78ff 100644 --- a/src/CMSMicroservice.Protobuf/Protos/transactions.proto +++ b/src/CMSMicroservice.Protobuf/Protos/transactions.proto @@ -43,6 +43,11 @@ service TransactionsContract }; }; + rpc GetTransactionSummary(GetTransactionSummaryRequest) returns (GetTransactionSummaryResponse){ + option (google.api.http) = { + get: "/GetTransactionSummary" + }; + }; rpc VerifyTransaction(VerifyTransactionRequest) returns (VerifyTransactionResponse){ option (google.api.http) = { post: "/VerifyTransaction" @@ -161,6 +166,10 @@ message GetAllTransactionsByFilterFilter { messages.TransactionType type = 7; } + google.protobuf.Timestamp created_from = 8; + google.protobuf.Timestamp created_to = 9; + google.protobuf.Timestamp payment_date_from = 10; + google.protobuf.Timestamp payment_date_to = 11; } message GetAllTransactionsByFilterResponse { @@ -182,6 +191,21 @@ message GetAllTransactionsByFilterResponseModel { messages.TransactionType type = 7; } + google.protobuf.Timestamp created = 8; +} + +message GetTransactionSummaryRequest +{ + GetAllTransactionsByFilterFilter filter = 1; +} + +message GetTransactionSummaryResponse +{ + int64 total_count = 1; + int64 total_amount = 2; + int64 success_count = 3; + int64 pending_count = 4; + int64 reject_count = 5; } // VerifyTransaction Messages diff --git a/src/CMSMicroservice.Protobuf/Protos/userpackagepurchase.proto b/src/CMSMicroservice.Protobuf/Protos/userpackagepurchase.proto new file mode 100644 index 0000000..8a52aa4 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/userpackagepurchase.proto @@ -0,0 +1,75 @@ +syntax = "proto3"; + +package userpackagepurchase; + +import "public_messages.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.UserPackagePurchase"; + +service UserPackagePurchaseContract +{ + rpc GetAllUserPackagePurchaseByFilter(GetAllUserPackagePurchaseByFilterRequest) returns (GetAllUserPackagePurchaseByFilterResponse){ + option (google.api.http) = { + get: "/UserPackagePurchase/GetAllByFilter" + }; + }; + rpc GetUserPackagePurchaseSummary(GetUserPackagePurchaseSummaryRequest) returns (GetUserPackagePurchaseSummaryResponse){ + option (google.api.http) = { + get: "/UserPackagePurchase/GetSummary" + }; + }; +} + +message GetAllUserPackagePurchaseByFilterRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllUserPackagePurchaseByFilterFilter filter = 3; +} + +message GetAllUserPackagePurchaseByFilterFilter +{ + google.protobuf.Int64Value user_id = 1; + google.protobuf.Int64Value package_id = 2; + google.protobuf.Int32Value purchase_method = 3; + google.protobuf.Timestamp purchased_from = 4; + google.protobuf.Timestamp purchased_to = 5; + google.protobuf.Int64Value min_amount = 6; + google.protobuf.Int64Value max_amount = 7; +} + +message GetAllUserPackagePurchaseByFilterResponse +{ + messages.MetaData meta_data = 1; + repeated UserPackagePurchaseModel models = 2; +} + +message UserPackagePurchaseModel +{ + int64 id = 1; + int64 user_id = 2; + string user_name = 3; + string user_mobile = 4; + int64 package_id = 5; + string package_name = 6; + int32 purchase_method = 7; + google.protobuf.Timestamp purchased_at = 8; + int64 amount = 9; + google.protobuf.Int64Value order_id = 10; + google.protobuf.Int64Value transaction_id = 11; + google.protobuf.Timestamp created_at = 12; +} + +message GetUserPackagePurchaseSummaryRequest +{ + GetAllUserPackagePurchaseByFilterFilter filter = 1; +} + +message GetUserPackagePurchaseSummaryResponse +{ + int64 total_count = 1; + int64 total_amount = 2; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto index c574685..ab9a1a8 100644 --- a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto +++ b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto @@ -110,6 +110,21 @@ service UserWalletContract body: "*" }; }; + + // ============= Credit (Main) Wallet Methods ============= + + rpc InitiateCreditCharge(InitiateCreditChargeRequest) returns (InitiateCreditChargeResponse){ + option (google.api.http) = { + post: "/Customer/InitiateCreditCharge" + body: "*" + }; + }; + rpc VerifyCreditCharge(VerifyWalletChargeRequest) returns (VerifyWalletChargeResponse){ + option (google.api.http) = { + post: "/Customer/VerifyCreditCharge" + body: "*" + }; + }; } message CreateNewUserWalletRequest { @@ -283,6 +298,20 @@ message InitiateDiscountChargeResponse string error_message = 3; } +// ============= Credit (Main) Wallet Messages ============= + +message InitiateCreditChargeRequest +{ + int64 amount = 1; // مبلغ واریزی (تومان) +} + +message InitiateCreditChargeResponse +{ + bool is_success = 1; + string gateway_url = 2; + string error_message = 3; +} + // ============= Wallet Verify Messages ============= message VerifyWalletChargeRequest diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipCycleHistoryProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipCycleHistoryProfile.cs new file mode 100644 index 0000000..8f6b32d --- /dev/null +++ b/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipCycleHistoryProfile.cs @@ -0,0 +1,47 @@ +using Google.Protobuf.WellKnownTypes; +using CMSMicroservice.Domain.Enums; +using Mapster; +using AppHistory = CMSMicroservice.Application.ClubMembershipCycleHistoryCQ.Queries.GetAllClubMembershipCycleHistoryByFilter; +using ProtoHistory = CMSMicroservice.Protobuf.Protos.ClubMembershipCycleHistory; + +namespace CMSMicroservice.WebApi.Common.Mappings; + +public class ClubMembershipCycleHistoryProfile : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.NewConfig() + .Map(dest => dest.PaginationState, src => src.PaginationState) + .Map(dest => dest.SortBy, src => src.SortBy) + .Map(dest => dest.Filter, src => src.Filter); + + config.NewConfig() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.ClubMembershipCycleId, src => src.ClubMembershipCycleId) + .Map(dest => dest.CycleNumber, src => src.CycleNumber) + .Map(dest => dest.Action, src => src.Action == null ? null : (ClubMembershipCycleAction?)src.Action) + .Map(dest => dest.CreatedFrom, src => src.CreatedFrom != null ? src.CreatedFrom.ToDateTime() : (DateTime?)null) + .Map(dest => dest.CreatedTo, src => src.CreatedTo != null ? src.CreatedTo.ToDateTime() : (DateTime?)null); + + config.NewConfig() + .Map(dest => dest.MetaData, src => src.MetaData) + .Map(dest => dest.Models, src => src.Models); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.ClubMembershipCycleId, src => src.ClubMembershipCycleId) + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.UserName, src => src.UserName) + .Map(dest => dest.CycleNumber, src => src.CycleNumber) + .Map(dest => dest.OldIsCurrentCycle, src => src.OldIsCurrentCycle) + .Map(dest => dest.NewIsCurrentCycle, src => src.NewIsCurrentCycle) + .Map(dest => dest.OldMagicStartedAt, src => src.OldMagicStartedAt) + .Map(dest => dest.NewMagicStartedAt, src => src.NewMagicStartedAt) + .Map(dest => dest.OldMagicCompletedAt, src => src.OldMagicCompletedAt) + .Map(dest => dest.NewMagicCompletedAt, src => src.NewMagicCompletedAt) + .Map(dest => dest.Action, src => (int)src.Action) + .Map(dest => dest.PerformedBy, src => src.PerformedBy ?? string.Empty) + .Map(dest => dest.Reason, src => src.Reason ?? string.Empty) + .Map(dest => dest.CreatedAt, src => Timestamp.FromDateTimeOffset(src.Created)); + } +} diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipCycleProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipCycleProfile.cs new file mode 100644 index 0000000..0d0fb48 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipCycleProfile.cs @@ -0,0 +1,57 @@ +using Google.Protobuf.WellKnownTypes; +using CMSMicroservice.Domain.Enums; +using Mapster; +using AppCycle = CMSMicroservice.Application.ClubMembershipCycleCQ.Queries.GetAllClubMembershipCycleByFilter; +using AppCycleSummary = CMSMicroservice.Application.ClubMembershipCycleCQ.Queries.GetClubMembershipCycleSummary; +using ProtoCycle = CMSMicroservice.Protobuf.Protos.ClubMembershipCycle; + +namespace CMSMicroservice.WebApi.Common.Mappings; + +public class ClubMembershipCycleProfile : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.NewConfig() + .Map(dest => dest.PaginationState, src => src.PaginationState) + .Map(dest => dest.SortBy, src => src.SortBy) + .Map(dest => dest.Filter, src => src.Filter); + + config.NewConfig() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.IsCurrentCycle, src => src.IsCurrentCycle) + .Map(dest => dest.PackageId, src => src.PackageId) + .Map(dest => dest.CycleNumber, src => src.CycleNumber) + .Map(dest => dest.PurchaseMethod, src => src.PurchaseMethod == null ? null : (PackagePurchaseMethod?)src.PurchaseMethod) + .Map(dest => dest.MagicState, src => src.MagicState) + .Map(dest => dest.PackagePurchasedFrom, src => src.PackagePurchasedFrom != null ? src.PackagePurchasedFrom.ToDateTime() : (DateTime?)null) + .Map(dest => dest.PackagePurchasedTo, src => src.PackagePurchasedTo != null ? src.PackagePurchasedTo.ToDateTime() : (DateTime?)null); + + config.NewConfig() + .Map(dest => dest.MetaData, src => src.MetaData) + .Map(dest => dest.Models, src => src.Models); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.UserName, src => src.UserName) + .Map(dest => dest.ClubMembershipId, src => src.ClubMembershipId) + .Map(dest => dest.CycleNumber, src => src.CycleNumber) + .Map(dest => dest.PackageId, src => src.PackageId) + .Map(dest => dest.PackageName, src => src.PackageName) + .Map(dest => dest.PackageAmount, src => src.PackageAmount) + .Map(dest => dest.PurchaseMethod, src => (int)src.PurchaseMethod) + .Map(dest => dest.PackagePurchasedAt, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.PackagePurchasedAt, DateTimeKind.Utc))) + .Map(dest => dest.MagicStartedAt, src => src.MagicStartedAt) + .Map(dest => dest.MagicCompletedAt, src => src.MagicCompletedAt) + .Map(dest => dest.IsCurrentCycle, src => src.IsCurrentCycle) + .Map(dest => dest.CreatedAt, src => Timestamp.FromDateTimeOffset(src.Created)); + + config.NewConfig() + .Map(dest => dest.Filter, src => src.Filter); + + config.NewConfig() + .Map(dest => dest.TotalCount, src => src.TotalCount) + .Map(dest => dest.CurrentCycleCount, src => src.CurrentCycleCount) + .Map(dest => dest.MagicActiveCount, src => src.MagicActiveCount); + } +} diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipProfile.cs index 170a483..3d99bd2 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/ClubMembershipProfile.cs @@ -1,7 +1,9 @@ using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships; using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership; using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics; -using CMSMicroservice.Protobuf.Protos.ClubMembership; +using CMSMicroservice.Domain.Enums; +using AppHistory = CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembershipHistory; +using ProtoClub = CMSMicroservice.Protobuf.Protos.ClubMembership; using Google.Protobuf.WellKnownTypes; using Mapster; using System.Linq; @@ -17,15 +19,15 @@ public class ClubMembershipProfile : IRegister // ===================== // GetClubMembershipRequest -> GetClubMembershipQuery - config.NewConfig() + config.NewConfig() .MapWith(src => new GetClubMembershipQuery { UserId = src.UserId }); // ClubMembershipDto -> GetClubMembershipResponse - config.NewConfig() - .MapWith(src => new GetClubMembershipResponse + config.NewConfig() + .MapWith(src => new ProtoClub.GetClubMembershipResponse { Id = src.Id, UserId = src.UserId, @@ -48,7 +50,7 @@ public class ClubMembershipProfile : IRegister // ===================== // GetAllClubMembershipsRequest -> GetAllClubMembershipsQuery - config.NewConfig() + config.NewConfig() .MapWith(src => new GetAllClubMembershipsQuery { PaginationState = new Application.Common.Models.PaginationState @@ -64,8 +66,8 @@ public class ClubMembershipProfile : IRegister }); // GetAllClubMembershipsResponseDto -> GetAllClubMembershipsResponse - config.NewConfig() - .MapWith(src => new GetAllClubMembershipsResponse + config.NewConfig() + .MapWith(src => new ProtoClub.GetAllClubMembershipsResponse { MetaData = new CMSMicroservice.Protobuf.Protos.MetaData { @@ -82,12 +84,12 @@ public class ClubMembershipProfile : IRegister // ===================== // GetClubStatisticsRequest -> GetClubStatisticsQuery - config.NewConfig() + config.NewConfig() .MapWith(src => new GetClubStatisticsQuery()); // GetClubStatisticsResponseDto -> GetClubStatisticsResponse - config.NewConfig() - .MapWith(src => new GetClubStatisticsResponse + config.NewConfig() + .MapWith(src => new ProtoClub.GetClubStatisticsResponse { TotalMembers = src.TotalMembers, ActiveMembers = src.ActiveMembers, @@ -97,14 +99,14 @@ public class ClubMembershipProfile : IRegister TotalRevenue = src.TotalRevenue, AverageMembershipDurationDays = src.AverageMembershipDurationDays, ExpiringSoonCount = src.ExpiringSoonCount, - PackageDistribution = { src.PackageDistribution.Select(p => new PackageLevelDistribution + PackageDistribution = { src.PackageDistribution.Select(p => new ProtoClub.PackageLevelDistribution { PackageId = p.PackageId, PackageName = p.PackageName ?? string.Empty, MemberCount = p.MemberCount, Percentage = p.Percentage }) }, - MonthlyTrend = { src.MonthlyTrend.Select(t => new MonthlyMembershipTrend + MonthlyTrend = { src.MonthlyTrend.Select(t => new ProtoClub.MonthlyMembershipTrend { Month = t.Month ?? string.Empty, Activations = t.Activations, @@ -112,24 +114,55 @@ public class ClubMembershipProfile : IRegister NetChange = t.NetChange }) } }); + + // GetClubMembershipHistory + config.NewConfig() + .Map(dest => dest.PaginationState, src => src.PaginationState) + .Map(dest => dest.SortBy, src => src.SortBy) + .Map(dest => dest.Filter, src => src.Filter); + + config.NewConfig() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.ClubMembershipId, src => src.ClubMembershipId) + .Map(dest => dest.Action, src => src.Action == null ? null : (ClubMembershipAction?)src.Action) + .Map(dest => dest.CreatedFrom, src => src.CreatedFrom != null ? src.CreatedFrom.ToDateTime() : (DateTime?)null) + .Map(dest => dest.CreatedTo, src => src.CreatedTo != null ? src.CreatedTo.ToDateTime() : (DateTime?)null); + + config.NewConfig() + .Map(dest => dest.MetaData, src => src.MetaData) + .Map(dest => dest.Models, src => src.Models); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.ClubMembershipId, src => src.ClubMembershipId) + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.UserName, src => src.UserName ?? string.Empty) + .Map(dest => dest.OldIsActive, src => src.OldIsActive) + .Map(dest => dest.NewIsActive, src => src.NewIsActive) + .Map(dest => dest.OldInitialContribution, src => src.OldInitialContribution) + .Map(dest => dest.NewInitialContribution, src => src.NewInitialContribution) + .Map(dest => dest.Action, src => (int)src.Action) + .Map(dest => dest.PerformedBy, src => src.PerformedBy ?? string.Empty) + .Map(dest => dest.Reason, src => src.Reason ?? string.Empty) + .Map(dest => dest.CreatedAt, src => Timestamp.FromDateTimeOffset(src.Created)); } - private static ClubMembershipModel MapToModel(GetAllClubMembershipsResponseModel src) + private static ProtoClub.ClubMembershipModel MapToModel(GetAllClubMembershipsResponseModel src) { - return new ClubMembershipModel + return new ProtoClub.ClubMembershipModel { Id = src.Id, UserId = src.UserId, UserName = src.UserName ?? string.Empty, - PackageId = 0, // Not available in current entity - PackageName = string.Empty, // Not available in current entity - ActivationCode = string.Empty, // Not available in current entity + PackageId = src.PackageId, + PackageName = src.PackageName ?? string.Empty, + ActivationCode = string.Empty, ActivatedAt = src.ActivatedAt.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.ActivatedAt.Value, DateTimeKind.Utc)) : null, - ExpiresAt = null, // Not available in current entity + ExpiresAt = null, IsActive = src.IsActive, - IsExpired = false, // Calculate based on ExpiresAt if available + IsExpired = false, Created = Timestamp.FromDateTime(src.Created.UtcDateTime) }; } diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/PaymentTransactionProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/PaymentTransactionProfile.cs new file mode 100644 index 0000000..00eba3c --- /dev/null +++ b/src/CMSMicroservice.WebApi/Common/Mappings/PaymentTransactionProfile.cs @@ -0,0 +1,60 @@ +using Google.Protobuf.WellKnownTypes; +using CMSMicroservice.Domain.Enums; +using Mapster; +using AppPt = CMSMicroservice.Application.PaymentTransactionCQ.Queries.GetAllPaymentTransactionByFilter; +using AppPtSummary = CMSMicroservice.Application.PaymentTransactionCQ.Queries.GetPaymentTransactionSummary; +using ProtoPt = CMSMicroservice.Protobuf.Protos.PaymentTransaction; + +namespace CMSMicroservice.WebApi.Common.Mappings; + +public class PaymentTransactionProfile : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.NewConfig() + .Map(dest => dest.PaginationState, src => src.PaginationState) + .Map(dest => dest.SortBy, src => src.SortBy) + .Map(dest => dest.Filter, src => src.Filter); + + config.NewConfig() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.Mobile, src => src.Mobile) + .Map(dest => dest.GatewayProvider, src => src.GatewayProvider) + .Map(dest => dest.PaymentStatus, src => src.PaymentStatus) + .Map(dest => dest.Authority, src => src.Authority) + .Map(dest => dest.RefId, src => src.RefId) + .Map(dest => dest.OrderId, src => src.OrderId) + .Map(dest => dest.CreatedFrom, src => src.CreatedFrom != null ? src.CreatedFrom.ToDateTime() : (DateTime?)null) + .Map(dest => dest.CreatedTo, src => src.CreatedTo != null ? src.CreatedTo.ToDateTime() : (DateTime?)null); + + config.NewConfig() + .Map(dest => dest.MetaData, src => src.MetaData) + .Map(dest => dest.Models, src => src.Models); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.UserId, src => src.UserId ?? 0) + .Map(dest => dest.Mobile, src => src.Mobile ?? string.Empty) + .Map(dest => dest.GatewayProvider, src => src.GatewayProvider) + .Map(dest => dest.Amount, src => src.Amount) + .Map(dest => dest.Authority, src => src.Authority ?? string.Empty) + .Map(dest => dest.PaymentStatus, src => src.PaymentStatus) + .Map(dest => dest.VerificationStatusCode, src => src.VerificationStatusCode) + .Map(dest => dest.VerificationStatusMessage, src => src.VerificationStatusMessage ?? string.Empty) + .Map(dest => dest.RefId, src => src.RefId ?? string.Empty) + .Map(dest => dest.CardPan, src => src.CardPan ?? string.Empty) + .Map(dest => dest.OrderId, src => src.OrderId ?? string.Empty) + .Map(dest => dest.TransactionId, src => src.TransactionId) + .Map(dest => dest.Description, src => src.Description ?? string.Empty) + .Map(dest => dest.CreatedAt, src => Timestamp.FromDateTimeOffset(src.Created)); + + config.NewConfig() + .Map(dest => dest.Filter, src => src.Filter); + + config.NewConfig() + .Map(dest => dest.TotalCount, src => src.TotalCount) + .Map(dest => dest.TotalAmount, src => src.TotalAmount) + .Map(dest => dest.SuccessfulCount, src => src.SuccessfulCount) + .Map(dest => dest.PendingCount, src => src.PendingCount); + } +} diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/TransactionsProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/TransactionsProfile.cs index 34689ba..f9228ad 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/TransactionsProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/TransactionsProfile.cs @@ -1,11 +1,55 @@ +using Google.Protobuf.WellKnownTypes; +using Mapster; +using AppTx = CMSMicroservice.Application.TransactionsCQ.Queries.GetAllTransactionsByFilter; +using AppTxSummary = CMSMicroservice.Application.TransactionsCQ.Queries.GetTransactionSummary; +using ProtoTx = CMSMicroservice.Protobuf.Protos.Transactions; + namespace CMSMicroservice.WebApi.Common.Mappings; public class TransactionsProfile : IRegister { - void IRegister.Register(TypeAdapterConfig config) - { - config.NewConfig() - .IgnoreIf((src, dest) => !src.Filter.HasPaymentStatus, dest => dest.Filter.PaymentStatus) - .IgnoreIf((src, dest) => !src.Filter.HasType, dest => dest.Filter.Type); + public void Register(TypeAdapterConfig config) + { + config.NewConfig() + .Map(dest => dest.PaginationState, src => src.PaginationState) + .Map(dest => dest.SortBy, src => src.SortBy) + .Map(dest => dest.Filter, src => src.Filter); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.Amount, src => src.Amount) + .Map(dest => dest.Description, src => src.Description) + .Map(dest => dest.PaymentStatus, src => src.HasPaymentStatus ? (Domain.Enums.PaymentStatus?)src.PaymentStatus : null) + .Map(dest => dest.PaymentDate, src => src.PaymentDate != null ? src.PaymentDate.ToDateTime() : (DateTime?)null) + .Map(dest => dest.RefId, src => src.RefId) + .Map(dest => dest.Type, src => src.HasType ? (Domain.Enums.TransactionType?)src.Type : null) + .Map(dest => dest.CreatedFrom, src => src.CreatedFrom != null ? src.CreatedFrom.ToDateTime() : (DateTime?)null) + .Map(dest => dest.CreatedTo, src => src.CreatedTo != null ? src.CreatedTo.ToDateTime() : (DateTime?)null) + .Map(dest => dest.PaymentDateFrom, src => src.PaymentDateFrom != null ? src.PaymentDateFrom.ToDateTime() : (DateTime?)null) + .Map(dest => dest.PaymentDateTo, src => src.PaymentDateTo != null ? src.PaymentDateTo.ToDateTime() : (DateTime?)null); + + config.NewConfig() + .Map(dest => dest.MetaData, src => src.MetaData) + .Map(dest => dest.Models, src => src.Models); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.Amount, src => src.Amount) + .Map(dest => dest.Description, src => src.Description) + .Map(dest => dest.PaymentStatus, src => src.PaymentStatus) + .Map(dest => dest.PaymentDate, src => src.PaymentDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.PaymentDate.Value, DateTimeKind.Utc)) : null) + .Map(dest => dest.RefId, src => src.RefId) + .Map(dest => dest.Type, src => src.Type) + .Map(dest => dest.Created, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc))); + + config.NewConfig() + .Map(dest => dest.Filter, src => src.Filter); + + config.NewConfig() + .Map(dest => dest.TotalCount, src => src.TotalCount) + .Map(dest => dest.TotalAmount, src => src.TotalAmount) + .Map(dest => dest.SuccessCount, src => src.SuccessCount) + .Map(dest => dest.PendingCount, src => src.PendingCount) + .Map(dest => dest.RejectCount, src => src.RejectCount); } } diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/UserPackagePurchaseProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/UserPackagePurchaseProfile.cs new file mode 100644 index 0000000..ba02b15 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Common/Mappings/UserPackagePurchaseProfile.cs @@ -0,0 +1,53 @@ +using Google.Protobuf.WellKnownTypes; +using CMSMicroservice.Domain.Enums; +using Mapster; +using AppUp = CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter; +using AppUpSummary = CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetUserPackagePurchaseSummary; +using ProtoUp = CMSMicroservice.Protobuf.Protos.UserPackagePurchase; + +namespace CMSMicroservice.WebApi.Common.Mappings; + +public class UserPackagePurchaseProfile : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.NewConfig() + .Map(dest => dest.PaginationState, src => src.PaginationState) + .Map(dest => dest.SortBy, src => src.SortBy) + .Map(dest => dest.Filter, src => src.Filter); + + config.NewConfig() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.PackageId, src => src.PackageId) + .Map(dest => dest.PurchaseMethod, src => src.PurchaseMethod == null ? null : (PackagePurchaseMethod?)src.PurchaseMethod) + .Map(dest => dest.PurchasedFrom, src => src.PurchasedFrom != null ? src.PurchasedFrom.ToDateTime() : (DateTime?)null) + .Map(dest => dest.PurchasedTo, src => src.PurchasedTo != null ? src.PurchasedTo.ToDateTime() : (DateTime?)null) + .Map(dest => dest.MinAmount, src => src.MinAmount) + .Map(dest => dest.MaxAmount, src => src.MaxAmount); + + config.NewConfig() + .Map(dest => dest.MetaData, src => src.MetaData) + .Map(dest => dest.Models, src => src.Models); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.UserName, src => src.UserName) + .Map(dest => dest.UserMobile, src => src.UserMobile) + .Map(dest => dest.PackageId, src => src.PackageId) + .Map(dest => dest.PackageName, src => src.PackageName) + .Map(dest => dest.PurchaseMethod, src => (int)src.PurchaseMethod) + .Map(dest => dest.PurchasedAt, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.PurchasedAt, DateTimeKind.Utc))) + .Map(dest => dest.Amount, src => src.Amount) + .Map(dest => dest.OrderId, src => src.OrderId) + .Map(dest => dest.TransactionId, src => src.TransactionId) + .Map(dest => dest.CreatedAt, src => Timestamp.FromDateTimeOffset(src.Created)); + + config.NewConfig() + .Map(dest => dest.Filter, src => src.Filter); + + config.NewConfig() + .Map(dest => dest.TotalCount, src => src.TotalCount) + .Map(dest => dest.TotalAmount, src => src.TotalAmount); + } +} diff --git a/src/CMSMicroservice.WebApi/Services/ClubMembershipCycleHistoryService.cs b/src/CMSMicroservice.WebApi/Services/ClubMembershipCycleHistoryService.cs new file mode 100644 index 0000000..0db9ae3 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/ClubMembershipCycleHistoryService.cs @@ -0,0 +1,16 @@ +using CMSMicroservice.Protobuf.Protos.ClubMembershipCycleHistory; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.ClubMembershipCycleHistoryCQ.Queries.GetAllClubMembershipCycleHistoryByFilter; + +namespace CMSMicroservice.WebApi.Services; + +public class ClubMembershipCycleHistoryService : ClubMembershipCycleHistoryContract.ClubMembershipCycleHistoryContractBase +{ + private readonly IDispatchRequestToCQRS _dispatch; + + public ClubMembershipCycleHistoryService(IDispatchRequestToCQRS dispatch) => _dispatch = dispatch; + + public override Task GetAllClubMembershipCycleHistoryByFilter( + GetAllClubMembershipCycleHistoryByFilterRequest request, ServerCallContext context) => + _dispatch.Handle(request, context); +} diff --git a/src/CMSMicroservice.WebApi/Services/ClubMembershipCycleService.cs b/src/CMSMicroservice.WebApi/Services/ClubMembershipCycleService.cs new file mode 100644 index 0000000..0cfc25a --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/ClubMembershipCycleService.cs @@ -0,0 +1,21 @@ +using CMSMicroservice.Protobuf.Protos.ClubMembershipCycle; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.ClubMembershipCycleCQ.Queries.GetAllClubMembershipCycleByFilter; +using CMSMicroservice.Application.ClubMembershipCycleCQ.Queries.GetClubMembershipCycleSummary; + +namespace CMSMicroservice.WebApi.Services; + +public class ClubMembershipCycleService : ClubMembershipCycleContract.ClubMembershipCycleContractBase +{ + private readonly IDispatchRequestToCQRS _dispatch; + + public ClubMembershipCycleService(IDispatchRequestToCQRS dispatch) => _dispatch = dispatch; + + public override Task GetAllClubMembershipCycleByFilter( + GetAllClubMembershipCycleByFilterRequest request, ServerCallContext context) => + _dispatch.Handle(request, context); + + public override Task GetClubMembershipCycleSummary( + GetClubMembershipCycleSummaryRequest request, ServerCallContext context) => + _dispatch.Handle(request, context); +} diff --git a/src/CMSMicroservice.WebApi/Services/PaymentTransactionService.cs b/src/CMSMicroservice.WebApi/Services/PaymentTransactionService.cs new file mode 100644 index 0000000..509b4e2 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/PaymentTransactionService.cs @@ -0,0 +1,21 @@ +using CMSMicroservice.Protobuf.Protos.PaymentTransaction; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.PaymentTransactionCQ.Queries.GetAllPaymentTransactionByFilter; +using CMSMicroservice.Application.PaymentTransactionCQ.Queries.GetPaymentTransactionSummary; + +namespace CMSMicroservice.WebApi.Services; + +public class PaymentTransactionService : PaymentTransactionContract.PaymentTransactionContractBase +{ + private readonly IDispatchRequestToCQRS _dispatch; + + public PaymentTransactionService(IDispatchRequestToCQRS dispatch) => _dispatch = dispatch; + + public override Task GetAllPaymentTransactionByFilter( + GetAllPaymentTransactionByFilterRequest request, ServerCallContext context) => + _dispatch.Handle(request, context); + + public override Task GetPaymentTransactionSummary( + GetPaymentTransactionSummaryRequest request, ServerCallContext context) => + _dispatch.Handle(request, context); +} diff --git a/src/CMSMicroservice.WebApi/Services/TransactionsService.cs b/src/CMSMicroservice.WebApi/Services/TransactionsService.cs index c88b48b..96c1ade 100644 --- a/src/CMSMicroservice.WebApi/Services/TransactionsService.cs +++ b/src/CMSMicroservice.WebApi/Services/TransactionsService.cs @@ -5,6 +5,7 @@ using CMSMicroservice.Application.TransactionsCQ.Commands.UpdateTransactions; using CMSMicroservice.Application.TransactionsCQ.Commands.DeleteTransactions; using CMSMicroservice.Application.TransactionsCQ.Queries.GetTransactions; using CMSMicroservice.Application.TransactionsCQ.Queries.GetAllTransactionsByFilter; +using CMSMicroservice.Application.TransactionsCQ.Queries.GetTransactionSummary; using CMSMicroservice.Application.TransactionsCQ.Commands.VerifyTransaction; using CMSMicroservice.Application.TransactionsCQ.Commands.RefundTransaction; using CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction; @@ -69,6 +70,10 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase { return await _dispatchRequestToCQRS.Handle(request, context); } + + public override Task GetTransactionSummary( + GetTransactionSummaryRequest request, ServerCallContext context) => + _dispatchRequestToCQRS.Handle(request, context); public override async Task VerifyTransaction(VerifyTransactionRequest request, ServerCallContext context) { diff --git a/src/CMSMicroservice.WebApi/Services/UserPackagePurchaseService.cs b/src/CMSMicroservice.WebApi/Services/UserPackagePurchaseService.cs new file mode 100644 index 0000000..239d7c8 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/UserPackagePurchaseService.cs @@ -0,0 +1,21 @@ +using CMSMicroservice.Protobuf.Protos.UserPackagePurchase; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter; +using CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetUserPackagePurchaseSummary; + +namespace CMSMicroservice.WebApi.Services; + +public class UserPackagePurchaseService : UserPackagePurchaseContract.UserPackagePurchaseContractBase +{ + private readonly IDispatchRequestToCQRS _dispatch; + + public UserPackagePurchaseService(IDispatchRequestToCQRS dispatch) => _dispatch = dispatch; + + public override Task GetAllUserPackagePurchaseByFilter( + GetAllUserPackagePurchaseByFilterRequest request, ServerCallContext context) => + _dispatch.Handle(request, context); + + public override Task GetUserPackagePurchaseSummary( + GetUserPackagePurchaseSummaryRequest request, ServerCallContext context) => + _dispatch.Handle(request, context); +} diff --git a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs index 3863aa5..72338f0 100644 --- a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs @@ -5,8 +5,10 @@ using CMSMicroservice.Application.UserWalletCQ.Commands.UpdateUserWallet; using CMSMicroservice.Application.UserWalletCQ.Commands.DeleteUserWallet; using CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet; using CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet; +using CMSMicroservice.Application.WalletCQ.Commands.ChargeCreditWallet; using CMSMicroservice.Application.WalletCQ.Commands.VerifyMagicWalletCharge; using CMSMicroservice.Application.WalletCQ.Commands.VerifyDiscountWalletCharge; +using CMSMicroservice.Application.WalletCQ.Commands.VerifyCreditWalletCharge; using CMSMicroservice.Application.UserWalletCQ.Queries.GetUserWallet; using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter; using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory; @@ -242,6 +244,38 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase } } + // ============= Credit (Main) Wallet Methods ============= + + public override async Task InitiateCreditCharge( + InitiateCreditChargeRequest request, ServerCallContext context) + { + var userId = GetCurrentUserId(); + + try + { + var result = await _sender.Send(new ChargeCreditWalletCommand + { + UserId = userId, + Amount = request.Amount + }, context.CancellationToken); + + return new InitiateCreditChargeResponse + { + IsSuccess = result.IsSuccess, + GatewayUrl = result.GatewayUrl ?? "", + ErrorMessage = result.ErrorMessage ?? "" + }; + } + catch (PaymentInProgressException ex) + { + return new InitiateCreditChargeResponse + { + IsSuccess = false, + ErrorMessage = ex.Message + }; + } + } + // ============= Wallet Verify Methods ============= public override async Task VerifyMagicCharge( @@ -330,6 +364,57 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase } } + public override async Task VerifyCreditCharge( + VerifyWalletChargeRequest request, ServerCallContext context) + { + _logger.LogInformation("VerifyCreditCharge called: Authority={Authority}, Status={Status}", + request.Authority, request.Status); + + try + { + if (string.IsNullOrEmpty(request.Authority)) + return new VerifyWalletChargeResponse { Success = false, Message = "کد Authority نامعتبر است" }; + + var paymentTx = await _context.PaymentTransactions + .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken); + + if (paymentTx == null || !paymentTx.UserId.HasValue) + { + _logger.LogError("VerifyCreditCharge: PaymentTransaction not found for Authority={Authority}", request.Authority); + return new VerifyWalletChargeResponse { Success = false, Message = "تراکنش یافت نشد" }; + } + + if (!string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogWarning("VerifyCreditCharge: Payment cancelled by user. Authority={Authority}", request.Authority); + return new VerifyWalletChargeResponse { Success = false, Message = "پرداخت توسط کاربر لغو شد" }; + } + + var result = await _sender.Send(new VerifyCreditWalletChargeCommand + { + UserId = paymentTx.UserId.Value, + Amount = paymentTx.Amount, + Authority = request.Authority + }, context.CancellationToken); + + _logger.LogInformation("VerifyCreditCharge result: {Result}, Authority={Authority}, UserId={UserId}", + result, request.Authority, paymentTx.UserId.Value); + + return new VerifyWalletChargeResponse + { + Success = result, + Message = result + ? "شارژ کیف پول اصلی با موفقیت انجام شد" + : "شارژ کیف پول اصلی ناموفق بود" + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "VerifyCreditCharge error: Authority={Authority}", request.Authority); + return new VerifyWalletChargeResponse { Success = false, Message = ex.Message }; + } + } + public override async Task GetMagicWalletStatus( Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context) {