using CMSMicroservice.Protobuf.Protos.Transactions; using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.TransactionsCQ.Commands.CreateNewTransactions; 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.Commands.VerifyTransaction; using CMSMicroservice.Application.TransactionsCQ.Commands.RefundTransaction; using CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction; using CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransactionsByFilter; using CMSMicroservice.Application.Common.Interfaces; using AppModels = CMSMicroservice.Application.Common.Models; using Grpc.Core; using MediatR; using Mapster; using Microsoft.EntityFrameworkCore; using System.Linq; namespace CMSMicroservice.WebApi.Services; public class TransactionsService : TransactionsContract.TransactionsContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; private readonly ISender _sender; private readonly IApplicationDbContext _context; private readonly ICurrentUserService _currentUserService; private readonly IPaymentGatewayService _paymentGateway; public TransactionsService( IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender, IApplicationDbContext context, ICurrentUserService currentUserService, IPaymentGatewayService paymentGateway) { _dispatchRequestToCQRS = dispatchRequestToCQRS; _sender = sender; _context = context; _currentUserService = currentUserService; _paymentGateway = paymentGateway; } public override async Task CreateNewTransactions(CreateNewTransactionsRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task UpdateTransactions(UpdateTransactionsRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task DeleteTransactions(DeleteTransactionsRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetTransactions(GetTransactionsRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetAllTransactionsByFilter(GetAllTransactionsByFilterRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task VerifyTransaction(VerifyTransactionRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task RefundTransaction(RefundTransactionRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } // ============= Customer-specific Methods ============= public override async Task GetCustomerTransaction(GetCustomerTransactionRequest request, ServerCallContext context) { var query = new GetCustomerTransactionQuery { Id = request.Id, Authority = request.Authority, UserId = 0 // از JWT دریافت می‌شود }; var result = await _sender.Send(query, context.CancellationToken); return new GetCustomerTransactionResponse { Id = result.Id, Amount = result.Amount, Description = result.Description, PaymentStatus = result.PaymentStatus == Domain.Enums.PaymentStatus.Success, RefId = result.RefId, Type = (TransactionTypeEnum)result.Type, Currency = CurrencyEnum.Irr }; } public override async Task GetCustomerTransactionsByFilter(GetCustomerTransactionsByFilterRequest request, ServerCallContext context) { var query = new GetCustomerTransactionsByFilterQuery { UserId = 0, // از JWT دریافت می‌شود PaginationState = request.PaginationState?.Adapt(), SortBy = request.SortBy, IdFilter = request.Filter?.Id, AmountFilter = request.Filter?.Amount, DescriptionFilter = request.Filter?.Description, PaymentStatusFilter = request.Filter?.PaymentStatus, RefIdFilter = request.Filter?.RefId, TypeFilter = request.Filter?.Type != null ? (int?)request.Filter.Type : null }; var result = await _sender.Send(query, context.CancellationToken); var response = new GetCustomerTransactionsByFilterResponse { MetaData = result.MetaData.Adapt() }; foreach (var model in result.Models) { response.Models.Add(new GetCustomerTransactionsByFilterResponseModel { Id = model.Id, Amount = model.Amount, Description = model.Description, PaymentStatus = model.PaymentStatus == Domain.Enums.PaymentStatus.Success, RefId = model.RefId, Type = (TransactionTypeEnum)model.Type, Currency = CurrencyEnum.Irr }); } return response; } public override async Task CustomerPaymentRequest(CustomerPaymentRequestRequest request, ServerCallContext context) { var userId = GetCurrentUserId(); // Get user mobile for payment gateway var user = await _context.Users .AsNoTracking() .Where(u => u.Id == userId) .Select(u => new { u.Mobile, u.Email }) .FirstOrDefaultAsync(context.CancellationToken); // Create transaction record in DB var transaction = new CMSMicroservice.Domain.Entities.Transaction { Amount = request.Amount, Description = request.Description ?? "پرداخت آنلاین", PaymentStatus = Domain.Enums.PaymentStatus.Pending, Type = Domain.Enums.TransactionType.DepositIpg }; _context.Transactions.Add(transaction); await _context.SaveChangesAsync(context.CancellationToken); // Initiate payment with gateway var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest { Amount = request.Amount, UserId = userId, Mobile = request.Mobile ?? user?.Mobile ?? string.Empty, Description = request.Description ?? "پرداخت آنلاین", CallbackUrl = request.CallbackUrl }, context.CancellationToken); if (!paymentResult.IsSuccess) { // Update transaction status to failed transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject; await _context.SaveChangesAsync(context.CancellationToken); throw new RpcException(new Status(StatusCode.Internal, paymentResult.ErrorMessage ?? "خطا در ارتباط با درگاه پرداخت")); } // Save RefId from gateway transaction.RefId = paymentResult.RefId; await _context.SaveChangesAsync(context.CancellationToken); return new CustomerPaymentRequestResponse { PaymentGWUrl = paymentResult.GatewayUrl ?? string.Empty }; } public override async Task CustomerPaymentVerification(CustomerPaymentVerificationRequest request, ServerCallContext context) { // Find the transaction by authority/refId var transaction = await _context.Transactions .Where(t => t.RefId == request.Authority && !t.IsDeleted) .FirstOrDefaultAsync(context.CancellationToken); if (transaction == null) throw new RpcException(new Status(StatusCode.NotFound, "تراکنش یافت نشد")); // If status from gateway callback is not OK, mark as rejected if (request.Status != "OK") { transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject; await _context.SaveChangesAsync(context.CancellationToken); return new CustomerPaymentVerificationResponse { Id = transaction.Id, PaymentStatus = false, Message = "پرداخت توسط کاربر لغو شد", VerificationStatusCode = -1 }; } // Verify with gateway var verifyResult = await _paymentGateway.VerifyPaymentAsync( request.Authority, request.Status, context.CancellationToken); if (verifyResult.IsSuccess) { transaction.PaymentStatus = Domain.Enums.PaymentStatus.Success; transaction.PaymentDate = DateTime.UtcNow; transaction.RefId = verifyResult.RefId; } else { transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject; } await _context.SaveChangesAsync(context.CancellationToken); return new CustomerPaymentVerificationResponse { Id = transaction.Id, PaymentStatus = verifyResult.IsSuccess, Message = verifyResult.IsSuccess ? "پرداخت با موفقیت انجام شد" : (verifyResult.Message ?? "پرداخت ناموفق"), RefId = verifyResult.RefId ?? string.Empty, OrderId = string.Empty, VerificationStatusCode = verifyResult.IsSuccess ? 100 : -1 }; } private long GetCurrentUserId() { if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0) return userId; throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید")); } }