From 094846ce8b1a2f7720199f2b1f21421c923f6c2a Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 12 Dec 2025 10:07:14 +0330 Subject: [PATCH] feat: add manual membership payment processing with wallet and order management --- .../ProcessManualMembershipPaymentCommand.cs | 29 +++ ...ssManualMembershipPaymentCommandHandler.cs | 180 ++++++++++++++++++ ...ManualMembershipPaymentCommandValidator.cs | 25 +++ ...ocessManualMembershipPaymentResponseDto.cs | 24 +++ .../Enums/PaymentMethod.cs | 1 + .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/manualpayment.proto | 24 +++ .../Services/ManualPaymentService.cs | 25 +++ 8 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommand.cs create mode 100644 src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandHandler.cs create mode 100644 src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandValidator.cs create mode 100644 src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentResponseDto.cs diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommand.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommand.cs new file mode 100644 index 0000000..fa553d3 --- /dev/null +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommand.cs @@ -0,0 +1,29 @@ +namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.ProcessManualMembershipPayment; + +public record ProcessManualMembershipPaymentCommand : IRequest +{ + /// + /// شناسه کاربر + /// + public long UserId { get; init; } + + /// + /// مبلغ پرداختی (ریال) + /// + public long Amount { get; init; } + + /// + /// شماره مرجع تراکنش + /// + public string ReferenceNumber { get; init; } = string.Empty; + + /// + /// توضیحات (اختیاری) + /// + public string? Description { get; init; } + + /// + /// شناسه ادمین ثبت کننده + /// + public long AdminUserId { get; init; } +} diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandHandler.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandHandler.cs new file mode 100644 index 0000000..912ba65 --- /dev/null +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandHandler.cs @@ -0,0 +1,180 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Enums; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.ProcessManualMembershipPayment; + +public class ProcessManualMembershipPaymentCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ILogger _logger; + + public ProcessManualMembershipPaymentCommandHandler( + IApplicationDbContext context, + ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task Handle( + ProcessManualMembershipPaymentCommand request, + CancellationToken cancellationToken) + { + try + { + _logger.LogInformation( + "Processing manual membership payment for UserId: {UserId}, Amount: {Amount}", + request.UserId, request.Amount); + + // 1. بررسی وجود کاربر + var user = await _context.Users + .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken); + + if (user == null) + { + _logger.LogError("User not found: {UserId}", request.UserId); + throw new NotFoundException($"کاربر با شناسه {request.UserId} یافت نشد"); + } + + // 2. بررسی وجود ادمین + var admin = await _context.Users + .FirstOrDefaultAsync(u => u.Id == request.AdminUserId, cancellationToken); + + if (admin == null) + { + _logger.LogError("Admin user not found: {AdminUserId}", request.AdminUserId); + throw new NotFoundException($"ادمین با شناسه {request.AdminUserId} یافت نشد"); + } + + // 3. پیدا کردن یا ایجاد کیف پول + var wallet = await _context.UserWallets + .FirstOrDefaultAsync(w => w.UserId == request.UserId, cancellationToken); + + if (wallet == null) + { + wallet = new UserWallet + { + UserId = request.UserId, + Balance = 0, + NetworkBalance = 0, + DiscountBalance = 0 + }; + await _context.UserWallets.AddAsync(wallet, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } + + // 4. افزودن به Balance و DiscountBalance + var oldBalance = wallet.Balance; + var oldDiscountBalance = wallet.DiscountBalance; + + wallet.Balance += request.Amount; + wallet.DiscountBalance += request.Amount; + + // 5. ثبت تراکنش + var transaction = new Transaction + { + Amount = request.Amount, + Description = $"پرداخت دستی عضویت - {request.Description ?? "بدون توضیحات"} - مرجع: {request.ReferenceNumber}", + PaymentStatus = PaymentStatus.Success, + PaymentDate = DateTime.Now, + RefId = request.ReferenceNumber, + Type = TransactionType.DepositExternal1 + }; + + _context.Transactions.Add(transaction); + await _context.SaveChangesAsync(cancellationToken); + + // 6. ثبت لاگ Balance + var balanceLog = new UserWalletChangeLog + { + WalletId = wallet.Id, + CurrentBalance = wallet.Balance, + ChangeValue = request.Amount, + CurrentNetworkBalance = wallet.NetworkBalance, + ChangeNerworkValue = 0, + CurrentDiscountBalance = oldDiscountBalance, + ChangeDiscountValue = 0, + IsIncrease = true, + RefrenceId = transaction.Id + }; + await _context.UserWalletChangeLogs.AddAsync(balanceLog, cancellationToken); + + // 7. ثبت لاگ DiscountBalance + // var discountLog = new UserWalletChangeLog + // { + // WalletId = wallet.Id, + // CurrentBalance = wallet.Balance, + // ChangeValue = 0, + // CurrentNetworkBalance = wallet.NetworkBalance, + // ChangeNerworkValue = 0, + // CurrentDiscountBalance = wallet.DiscountBalance, + // ChangeDiscountValue = request.Amount, + // IsIncrease = true, + // RefrenceId = transaction.Id + // }; + // await _context.UserWalletChangeLogs.AddAsync(discountLog, cancellationToken); + + // 8. پیدا کردن یا ایجاد آدرس پیشفرض کاربر + var userAddress = await _context.UserAddresses + .Where(a => a.UserId == request.UserId) + .OrderByDescending(a => a.IsDefault) + .ThenBy(a => a.Id) + .FirstOrDefaultAsync(cancellationToken); + + if (userAddress == null) + { + userAddress = new UserAddress + { + UserId = request.UserId, + Title = "آدرس پیشفرض", + Address = "پرداخت دستی عضویت - آدرس موقت", + PostalCode = "0000000000", + IsDefault = true, + CityId = 1 + }; + await _context.UserAddresses.AddAsync(userAddress, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } + + // 9. ثبت سفارش + var order = new UserOrder + { + UserId = request.UserId, + Amount = request.Amount, + TransactionId = transaction.Id, + PaymentStatus = PaymentStatus.Success, + PaymentDate = DateTime.Now, + PaymentMethod = PaymentMethod.IPG, + DeliveryStatus = DeliveryStatus.None, + PackageId = 3, + UserAddressId = userAddress.Id, + DeliveryDescription = $"پرداخت دستی عضویت توسط ادمین {admin.FirstName} {admin.LastName} - مرجع: {request.ReferenceNumber}" + }; + + _context.UserOrders.Add(order); + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation( + "Manual membership payment processed successfully. UserId: {UserId}, Amount: {Amount}, TransactionId: {TransactionId}, OrderId: {OrderId}, AdminUserId: {AdminUserId}", + request.UserId, request.Amount, transaction.Id, order.Id, request.AdminUserId); + + return new ProcessManualMembershipPaymentResponseDto + { + TransactionId = transaction.Id, + OrderId = order.Id, + NewWalletBalance = wallet.Balance, + Message = "پرداخت دستی با موفقیت ثبت شد" + }; + } + catch (Exception ex) when (ex is not NotFoundException) + { + _logger.LogError(ex, + "Error processing manual membership payment for UserId: {UserId}, Amount: {Amount}", + request.UserId, request.Amount); + throw; + } + } +} diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandValidator.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandValidator.cs new file mode 100644 index 0000000..60fb96f --- /dev/null +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentCommandValidator.cs @@ -0,0 +1,25 @@ +namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.ProcessManualMembershipPayment; + +public class ProcessManualMembershipPaymentCommandValidator : AbstractValidator +{ + public ProcessManualMembershipPaymentCommandValidator() + { + RuleFor(x => x.UserId) + .GreaterThan(0) + .WithMessage("شناسه کاربر معتبر نیست"); + + RuleFor(x => x.Amount) + .GreaterThan(0) + .WithMessage("مبلغ باید بزرگتر از صفر باشد"); + + RuleFor(x => x.ReferenceNumber) + .NotEmpty() + .WithMessage("شماره مرجع الزامی است") + .MaximumLength(50) + .WithMessage("شماره مرجع نباید بیشتر از 50 کاراکتر باشد"); + + RuleFor(x => x.AdminUserId) + .GreaterThan(0) + .WithMessage("شناسه ادمین معتبر نیست"); + } +} diff --git a/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentResponseDto.cs b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentResponseDto.cs new file mode 100644 index 0000000..7413e1d --- /dev/null +++ b/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/ProcessManualMembershipPayment/ProcessManualMembershipPaymentResponseDto.cs @@ -0,0 +1,24 @@ +namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.ProcessManualMembershipPayment; + +public class ProcessManualMembershipPaymentResponseDto +{ + /// + /// شناسه تراکنش ثبت شده + /// + public long TransactionId { get; set; } + + /// + /// شناسه سفارش ثبت شده + /// + public long OrderId { get; set; } + + /// + /// موجودی جدید کیف پول + /// + public long NewWalletBalance { get; set; } + + /// + /// پیام موفقیت + /// + public string Message { get; set; } = "پرداخت دستی با موفقیت ثبت شد"; +} diff --git a/src/CMSMicroservice.Domain/Enums/PaymentMethod.cs b/src/CMSMicroservice.Domain/Enums/PaymentMethod.cs index 73de3ea..93c59b4 100644 --- a/src/CMSMicroservice.Domain/Enums/PaymentMethod.cs +++ b/src/CMSMicroservice.Domain/Enums/PaymentMethod.cs @@ -4,4 +4,5 @@ public enum PaymentMethod { IPG = 0, Wallet = 1, + Deposit = 2, } diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index ea9d226..64682a5 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.148 + 0.0.149 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto b/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto index cf01439..715abe6 100644 --- a/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto +++ b/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto @@ -38,6 +38,13 @@ service ManualPaymentContract get: "/GetAllManualPayments" }; }; + + rpc ProcessManualMembershipPayment(ProcessManualMembershipPaymentRequest) returns (ProcessManualMembershipPaymentResponse){ + option (google.api.http) = { + post: "/ProcessManualMembershipPayment" + body: "*" + }; + }; } // Enums mirroring CMSMicroservice.Domain.Enums.ManualPaymentType @@ -128,3 +135,20 @@ message ManualPaymentModel google.protobuf.Timestamp created = 19; } +message ProcessManualMembershipPaymentRequest +{ + int64 user_id = 1; + int64 amount = 2; + string reference_number = 3; + google.protobuf.StringValue description = 4; + int64 admin_user_id = 5; +} + +message ProcessManualMembershipPaymentResponse +{ + int64 transaction_id = 1; + int64 order_id = 2; + int64 new_wallet_balance = 3; + string message = 4; +} + diff --git a/src/CMSMicroservice.WebApi/Services/ManualPaymentService.cs b/src/CMSMicroservice.WebApi/Services/ManualPaymentService.cs index 2206ab0..1375ea6 100644 --- a/src/CMSMicroservice.WebApi/Services/ManualPaymentService.cs +++ b/src/CMSMicroservice.WebApi/Services/ManualPaymentService.cs @@ -3,6 +3,7 @@ using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.ManualPaymentCQ.Commands.CreateManualPayment; using CMSMicroservice.Application.ManualPaymentCQ.Commands.ApproveManualPayment; using CMSMicroservice.Application.ManualPaymentCQ.Commands.RejectManualPayment; +using CMSMicroservice.Application.ManualPaymentCQ.Commands.ProcessManualMembershipPayment; using CMSMicroservice.Application.ManualPaymentCQ.Queries.GetAllManualPayments; using Grpc.Core; using Mapster; @@ -56,4 +57,28 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB { return await _dispatchRequestToCQRS.Handle(request, context); } + + public override async Task ProcessManualMembershipPayment( + ProcessManualMembershipPaymentRequest request, + ServerCallContext context) + { + var command = new ProcessManualMembershipPaymentCommand + { + UserId = request.UserId, + Amount = request.Amount, + ReferenceNumber = request.ReferenceNumber, + Description = request.Description, + AdminUserId = request.AdminUserId + }; + + var result = await _sender.Send(command, context.CancellationToken); + + return new ProcessManualMembershipPaymentResponse + { + TransactionId = result.TransactionId, + OrderId = result.OrderId, + NewWalletBalance = result.NewWalletBalance, + Message = result.Message + }; + } }