using System.Data; using CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership; using CMSMicroservice.Application.Common; using CMSMicroservice.Application.Common.Exceptions; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Entities; using CMSMicroservice.Domain.Entities.Payment; using CMSMicroservice.Domain.Enums; using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyUserPackagePurchasePayment; public class VerifyUserPackagePurchasePaymentCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IPaymentGatewayService _paymentGateway; private readonly ISender _sender; private readonly IUserPaymentLock _paymentLock; private readonly ILogger _logger; public VerifyUserPackagePurchasePaymentCommandHandler( IApplicationDbContext context, IPaymentGatewayService paymentGateway, ISender sender, IUserPaymentLock paymentLock, ILogger logger) { _context = context; _paymentGateway = paymentGateway; _sender = sender; _paymentLock = paymentLock; _logger = logger; } public async Task Handle( VerifyUserPackagePurchasePaymentCommand request, CancellationToken cancellationToken) { var purchaseUserId = await _context.UserPackagePurchases .Where(p => p.Id == request.PurchaseId && !p.IsDeleted) .Select(p => (long?)p.UserId) .FirstOrDefaultAsync(cancellationToken); if (purchaseUserId is null or 0) throw new NotFoundException("سفارش یافت نشد"); var verifyKey = !string.IsNullOrEmpty(request.Authority) ? request.Authority : request.PurchaseId.ToString(); return await _paymentLock.ExecuteAsync( PaymentLockScopes.Verify(purchaseUserId.Value, verifyKey), PaymentLockStrategy.WaitForRelease, ct => HandleCoreAsync(request, ct), cancellationToken); } private async Task HandleCoreAsync( VerifyUserPackagePurchasePaymentCommand request, CancellationToken ct) { var purchase = await _context.UserPackagePurchases .Include(p => p.Package) .Where(p => p.Id == request.PurchaseId && !p.IsDeleted) .FirstOrDefaultAsync(ct); if (purchase == null) throw new NotFoundException("سفارش یافت نشد"); var transaction = purchase.TransactionId.HasValue ? await _context.Transactions .FirstOrDefaultAsync(t => t.Id == purchase.TransactionId.Value, ct) : null; var paymentTx = !string.IsNullOrEmpty(request.Authority) ? await _context.PaymentTransactions .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, ct) : null; if (IsPaymentAlreadyCompleted(transaction, paymentTx)) { return BuildResult(purchase, transaction, paymentTx, alreadyPaid: true); } if (!string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase)) { if (transaction != null && transaction.PaymentStatus != PaymentStatus.Success) { transaction.PaymentStatus = PaymentStatus.Reject; await _context.SaveChangesAsync(ct); } return new VerifyUserPackagePurchasePaymentResult { Success = false, Message = "پرداخت توسط کاربر لغو شد", PackageId = purchase.PackageId, PackageName = purchase.Package?.Title ?? string.Empty, AmountPaid = purchase.Amount, PurchasedAt = purchase.PurchasedAt }; } var amountInToman = paymentTx?.Amount ?? purchase.Amount; var verifyResult = await _paymentGateway.VerifyPaymentAsync( request.Authority, request.Status, (decimal)amountInToman, ct); if (!verifyResult.IsSuccess) { if (paymentTx != null) { paymentTx.PaymentStatus = false; paymentTx.VerificationStatusCode = verifyResult.VerificationCode; paymentTx.VerificationStatusMessage = verifyResult.Message; } if (transaction != null) transaction.PaymentStatus = PaymentStatus.Reject; await _context.SaveChangesAsync(ct); return new VerifyUserPackagePurchasePaymentResult { Success = false, Message = verifyResult.Message ?? "خرید پکیج ناموفق بود", TransactionId = transaction?.Id ?? 0, PackageId = purchase.PackageId, PackageName = purchase.Package?.Title ?? string.Empty, AmountPaid = purchase.Amount, PurchasedAt = purchase.PurchasedAt }; } await using var dbTx = await _context.Database.BeginTransactionAsync(IsolationLevel.Serializable, ct); try { if (purchase.TransactionId.HasValue) { transaction = await _context.Transactions .FirstOrDefaultAsync(t => t.Id == purchase.TransactionId.Value, ct); } if (!string.IsNullOrEmpty(request.Authority)) { paymentTx = await _context.PaymentTransactions .FirstOrDefaultAsync(pt => pt.Authority == request.Authority, ct); } if (IsPaymentAlreadyCompleted(transaction, paymentTx)) { await dbTx.CommitAsync(ct); return BuildResult(purchase, transaction, paymentTx, alreadyPaid: true); } if (transaction == null) throw new InvalidOperationException("تراکنش سفارش یافت نشد"); var alreadyCredited = await _context.UserWalletHistories .AnyAsync(h => h.RefrenceId == transaction.Id && !h.IsDeleted, ct); if (alreadyCredited) { transaction.PaymentStatus = PaymentStatus.Success; transaction.PaymentDate ??= DateTime.UtcNow; UpdatePaymentTransaction(paymentTx, verifyResult, markSuccess: true); await _context.SaveChangesAsync(ct); await dbTx.CommitAsync(ct); return BuildResult(purchase, transaction, paymentTx, alreadyPaid: true); } UpdatePaymentTransaction(paymentTx, verifyResult, markSuccess: true); transaction.PaymentStatus = PaymentStatus.Success; transaction.PaymentDate = DateTime.UtcNow; transaction.RefId = verifyResult.RefId; var wallet = await _context.UserWallets .FirstOrDefaultAsync(w => w.UserId == purchase.UserId, ct); if (wallet == null) { wallet = new UserWallet { UserId = purchase.UserId, Balance = 0, DiscountBalance = 0, NetworkBalance = 0 }; _context.UserWallets.Add(wallet); await _context.SaveChangesAsync(ct); } var discountMultiplier = purchase.Package?.DiscountMultiplier ?? throw new InvalidOperationException("پکیج سفارش یافت نشد"); var discountAmount = (long)(purchase.Amount * (double)discountMultiplier); wallet.Balance += purchase.Amount; wallet.DiscountBalance += discountAmount; _context.UserWalletHistories.Add(new UserWalletHistory { WalletId = wallet.Id, CurrentBalance = wallet.Balance, ChangeValue = purchase.Amount, CurrentNetworkBalance = wallet.NetworkBalance, ChangeNerworkValue = 0, CurrentDiscountBalance = wallet.DiscountBalance, ChangeDiscountValue = discountAmount, IsIncrease = true, RefrenceId = transaction.Id, PackageId = purchase.PackageId }); var user = await _context.Users .FirstOrDefaultAsync(u => u.Id == purchase.UserId, ct); if (user != null) user.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase; await _context.SaveChangesAsync(ct); await dbTx.CommitAsync(ct); } catch { await dbTx.RollbackAsync(ct); throw; } try { await _sender.Send(new ActivateClubMembershipCommand { UserId = purchase.UserId, ForceActivation = false }, ct); } catch (Exception ex) { _logger.LogWarning( ex, "Auto club activation failed after package verify. UserId={UserId}, PurchaseId={PurchaseId}", purchase.UserId, purchase.Id); } _logger.LogInformation( "Package purchase verified. PurchaseId={PurchaseId}, UserId={UserId}, Authority={Authority}", purchase.Id, purchase.UserId, request.Authority); return BuildResult( purchase, transaction, paymentTx, alreadyPaid: false, verifyResult.RefId); } private static bool IsPaymentAlreadyCompleted(Transaction? transaction, PaymentTransaction? paymentTx) { return transaction?.PaymentStatus == PaymentStatus.Success || paymentTx?.PaymentStatus == true; } private static void UpdatePaymentTransaction( PaymentTransaction? paymentTx, PaymentVerificationResult verifyResult, bool markSuccess) { if (paymentTx == null) return; paymentTx.PaymentStatus = markSuccess && verifyResult.IsSuccess; paymentTx.VerificationStatusCode = verifyResult.VerificationCode; paymentTx.VerificationStatusMessage = verifyResult.Message; paymentTx.CardPan = verifyResult.CardPan; paymentTx.CardHash = verifyResult.CardHash; paymentTx.RefId = verifyResult.TrackingCode; } private static VerifyUserPackagePurchasePaymentResult BuildResult( UserPackagePurchase purchase, Transaction? transaction, PaymentTransaction? paymentTx, bool alreadyPaid, string? referenceCode = null) { var refCode = referenceCode ?? paymentTx?.RefId ?? transaction?.RefId ?? string.Empty; return new VerifyUserPackagePurchasePaymentResult { Success = true, AlreadyPaid = alreadyPaid, Message = alreadyPaid ? "پرداخت قبلاً تأیید شده بود" : "خرید پکیج با موفقیت تایید شد", TransactionId = transaction?.Id ?? 0, ReferenceCode = refCode, PackageId = purchase.PackageId, PackageName = purchase.Package?.Title ?? string.Empty, AmountPaid = purchase.Amount, PurchasedAt = purchase.PurchasedAt }; } }