feat: Update payment processing and callback mechanisms
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m22s

- Refactor ActivateClubMembershipCommandHandler to use UserPackagePurchases instead of UserOrders for package activation.
- Modify PlaceOrderCommandHandler to redirect payment callbacks to the front office.
- Update ChargeDiscountWalletCommandHandler and ChargeMagicWalletCommandHandler to direct payment callbacks to the front office.
- Remove PaymentCallbackController and integrate payment verification directly into DiscountOrderService and UserWalletService.
- Add CustomerVerifyDiscountOrderPayment RPC to DiscountOrderService for verifying discount order payments.
- Implement VerifyMagicCharge and VerifyDiscountCharge methods in UserWalletService for wallet charge verifications.
- Update appsettings.json to use local URLs for development.
- Remove appsettings.Development.json as it is no longer needed.
- Comment out history tracking methods in ClubMembershipCycle and Package classes.
- Update PackageService to automatically activate club membership after successful payment verification.
- Adjust UserService to generate JWT tokens with user details.
This commit is contained in:
masoodafar-web
2026-02-28 03:27:46 +03:30
parent 01073084ab
commit 1e7c17f090
17 changed files with 363 additions and 334 deletions
@@ -13,6 +13,8 @@ using Grpc.Core;
using Google.Protobuf.WellKnownTypes;
using Mapster;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.WebApi.Services;
@@ -21,15 +23,24 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
private readonly ISender _sender;
private readonly ICurrentUserService _currentUserService;
private readonly IApplicationDbContext _context;
private readonly IPaymentGatewayService _paymentGateway;
private readonly ILogger<DiscountOrderService> _logger;
public DiscountOrderService(
IDispatchRequestToCQRS dispatchRequestToCQRS,
ISender sender,
ICurrentUserService currentUserService)
ICurrentUserService currentUserService,
IApplicationDbContext context,
IPaymentGatewayService paymentGateway,
ILogger<DiscountOrderService> logger)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
_sender = sender;
_currentUserService = currentUserService;
_context = context;
_paymentGateway = paymentGateway;
_logger = logger;
}
private long GetCurrentUserId()
@@ -174,6 +185,108 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
return await _dispatchRequestToCQRS.Handle<GetDiscountSalesReportRequest, GetDiscountSalesReportQuery, GetDiscountSalesReportResponse>(request, context);
}
// ============= Customer Verify Discount Order Payment =============
public override async Task<CustomerVerifyDiscountOrderPaymentResponse> CustomerVerifyDiscountOrderPayment(
CustomerVerifyDiscountOrderPaymentRequest request, ServerCallContext context)
{
_logger.LogInformation(
"CustomerVerifyDiscountOrderPayment called: OrderId={OrderId}, Authority={Authority}, Status={Status}",
request.OrderId, request.Authority, request.Status);
try
{
// پیدا کردن سفارش و تراکنش
var order = await _context.DiscountOrders
.Include(o => o.OrderDetails)
.FirstOrDefaultAsync(o => o.Id == request.OrderId, context.CancellationToken);
if (order == null)
{
_logger.LogError("Order #{OrderId} not found", request.OrderId);
return new CustomerVerifyDiscountOrderPaymentResponse
{
Success = false,
Message = "سفارش یافت نشد",
OrderId = request.OrderId
};
}
var transaction = order.TransactionId.HasValue
? await _context.Transactions.FirstOrDefaultAsync(
t => t.Id == order.TransactionId.Value, context.CancellationToken)
: null;
// تأیید پرداخت از درگاه
bool paymentSuccess = false;
string? refId = null;
if (string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrEmpty(request.Authority))
{
// Verify با مبلغ از دیتابیس (تومان — تبدیل به ریال در ZarinPalService)
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
request.Authority,
request.Status,
order.GatewayAmountPaid,
context.CancellationToken);
paymentSuccess = verifyResult.IsSuccess;
refId = verifyResult.TrackingCode ?? verifyResult.RefId;
// آپدیت PaymentTransaction با نتیجه verify
var paymentTx = await _context.PaymentTransactions
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.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;
await _context.SaveChangesAsync(context.CancellationToken);
}
_logger.LogInformation(
"Payment verification for Order #{OrderId}: Success={Success}, RefId={RefId}",
request.OrderId, paymentSuccess, refId);
}
else
{
_logger.LogWarning("Payment cancelled by user for Order #{OrderId}", request.OrderId);
}
// تکمیل سفارش از طریق CQRS
var completeResult = await _sender.Send(new CompleteOrderPaymentCommand
{
OrderId = request.OrderId,
TransactionId = transaction?.Id ?? 0,
PaymentSuccess = paymentSuccess,
RefId = refId
}, context.CancellationToken);
return new CustomerVerifyDiscountOrderPaymentResponse
{
Success = paymentSuccess && completeResult.Success,
Message = paymentSuccess && completeResult.Success
? "پرداخت سفارش با موفقیت انجام شد"
: "پرداخت سفارش ناموفق بود",
OrderId = request.OrderId
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Payment verify error for Order #{OrderId}", request.OrderId);
return new CustomerVerifyDiscountOrderPaymentResponse
{
Success = false,
Message = $"خطا در بررسی پرداخت: {ex.Message}",
OrderId = request.OrderId
};
}
}
private static CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus MapPaymentStatus(DomainEnums.PaymentStatus status) => status switch
{
DomainEnums.PaymentStatus.Success => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentCompleted,
@@ -351,6 +351,24 @@ public class PackageService : PackageContract.PackageContractBase
await _context.SaveChangesAsync(context.CancellationToken);
// فعالسازی خودکار باشگاه مشتریان بعد از تأیید پرداخت موفق
if (verifyResult.IsSuccess)
{
try
{
await _sender.Send(new CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership.ActivateClubMembershipCommand
{
UserId = purchase.UserId,
ForceActivation = false
}, context.CancellationToken);
}
catch (Exception ex)
{
// لاگ خطا ولی پرداخت موفق بوده — کاربر می‌تونه دستی فعالسازی کنه
System.Console.WriteLine($"Auto club activation failed for UserId {purchase.UserId}: {ex.Message}");
}
}
return new CustomerVerifyPackagePurchaseResponse
{
Success = verifyResult.IsSuccess,
@@ -604,7 +604,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
};
}
[RequiresPermission(PermissionNames.OrdersView)]
// [RequiresPermission(PermissionNames.OrdersView)]
public override async Task<CalculateOrderPVResponse> CalculateOrderPV(CalculateOrderPVRequest request, ServerCallContext context)
{
var order = await _context.UserOrders
@@ -35,6 +35,7 @@ public class UserService : UserContract.UserContractBase
private readonly ICurrentUserService _currentUserService;
private readonly IHashService _hashService;
private readonly CMSMicroservice.Application.Common.FileManager.IFileManager _fileManager;
private readonly IGenerateJwtToken _generateJwt;
public UserService(
IDispatchRequestToCQRS dispatchRequestToCQRS,
@@ -42,7 +43,8 @@ public class UserService : UserContract.UserContractBase
IApplicationDbContext context,
ICurrentUserService currentUserService,
IHashService hashService,
CMSMicroservice.Application.Common.FileManager.IFileManager fileManager)
CMSMicroservice.Application.Common.FileManager.IFileManager fileManager,
IGenerateJwtToken generateJwt)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
_sender = sender;
@@ -50,6 +52,7 @@ public class UserService : UserContract.UserContractBase
_currentUserService = currentUserService;
_hashService = hashService;
_fileManager = fileManager;
_generateJwt = generateJwt;
}
public override async Task<CreateNewUserResponse> CreateNewUser(CreateNewUserRequest request, ServerCallContext context)
{
@@ -115,12 +118,20 @@ public class UserService : UserContract.UserContractBase
var user = await _context.Users
.AsNoTracking()
.Include(u => u.UserContracts)
.ThenInclude(uc => uc.Contract)
.Include(u => u.UserRoles)
.ThenInclude(ur => ur.Role)
.Include(u => u.ClubMembership)
.Where(u => u.Id == userId && !u.IsDeleted)
.FirstOrDefaultAsync(context.CancellationToken);
if (user == null)
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
// تولید توکن JWT با آخرین اطلاعات کاربر
var token = await _generateJwt.GenerateJwtToken(user);
return new GetUserForCustomerResponse
{
Id = user.Id,
@@ -141,7 +152,8 @@ public class UserService : UserContract.UserContractBase
PushNotifications = user.PushNotifications,
BirthDate = user.BirthDate.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(user.BirthDate.Value, DateTimeKind.Utc))
: null
: null,
Token = token
};
}
@@ -5,6 +5,8 @@ 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.VerifyMagicWalletCharge;
using CMSMicroservice.Application.WalletCQ.Commands.VerifyDiscountWalletCharge;
using CMSMicroservice.Application.UserWalletCQ.Queries.GetUserWallet;
using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter;
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory;
@@ -16,6 +18,7 @@ using CMSMicroservice.Domain.Enums;
using Grpc.Core;
using Google.Protobuf.WellKnownTypes;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Linq;
namespace CMSMicroservice.WebApi.Services;
@@ -25,17 +28,20 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
private readonly ISender _sender;
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUserService;
private readonly ILogger<UserWalletService> _logger;
public UserWalletService(
IDispatchRequestToCQRS dispatchRequestToCQRS,
ISender sender,
IApplicationDbContext context,
ICurrentUserService currentUserService)
ICurrentUserService currentUserService,
ILogger<UserWalletService> logger)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
_sender = sender;
_context = context;
_currentUserService = currentUserService;
_logger = logger;
}
public override async Task<CreateNewUserWalletResponse> CreateNewUserWallet(CreateNewUserWalletRequest request, ServerCallContext context)
{
@@ -213,6 +219,94 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
};
}
// ============= Wallet Verify Methods =============
public override async Task<VerifyWalletChargeResponse> VerifyMagicCharge(
VerifyWalletChargeRequest request, ServerCallContext context)
{
_logger.LogInformation("VerifyMagicCharge called: Authority={Authority}, Status={Status}",
request.Authority, request.Status);
try
{
if (string.IsNullOrEmpty(request.Authority))
return new VerifyWalletChargeResponse { Success = false, Message = "کد Authority نامعتبر است" };
var result = await _sender.Send(new VerifyMagicWalletChargeCommand
{
Authority = request.Authority,
Status = request.Status ?? "NOK"
}, context.CancellationToken);
_logger.LogInformation("VerifyMagicCharge result: {Result}, Authority={Authority}", result, request.Authority);
return new VerifyWalletChargeResponse
{
Success = result,
Message = result
? "شارژ کیف‌پول جادویی با موفقیت انجام شد"
: "شارژ کیف‌پول جادویی ناموفق بود"
};
}
catch (Exception ex)
{
_logger.LogError(ex, "VerifyMagicCharge error: Authority={Authority}", request.Authority);
return new VerifyWalletChargeResponse { Success = false, Message = ex.Message };
}
}
public override async Task<VerifyWalletChargeResponse> VerifyDiscountCharge(
VerifyWalletChargeRequest request, ServerCallContext context)
{
_logger.LogInformation("VerifyDiscountCharge called: Authority={Authority}, Status={Status}",
request.Authority, request.Status);
try
{
if (string.IsNullOrEmpty(request.Authority))
return new VerifyWalletChargeResponse { Success = false, Message = "کد Authority نامعتبر است" };
// پیدا کردن PaymentTransaction برای استخراج UserId و Amount
var paymentTx = await _context.PaymentTransactions
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken);
if (paymentTx == null || !paymentTx.UserId.HasValue)
{
_logger.LogError("VerifyDiscountCharge: PaymentTransaction not found for Authority={Authority}", request.Authority);
return new VerifyWalletChargeResponse { Success = false, Message = "تراکنش یافت نشد" };
}
if (!string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning("VerifyDiscountCharge: Payment cancelled by user. Authority={Authority}", request.Authority);
return new VerifyWalletChargeResponse { Success = false, Message = "پرداخت توسط کاربر لغو شد" };
}
var result = await _sender.Send(new VerifyDiscountWalletChargeCommand
{
UserId = paymentTx.UserId.Value,
Amount = paymentTx.Amount,
Authority = request.Authority
}, context.CancellationToken);
_logger.LogInformation("VerifyDiscountCharge 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, "VerifyDiscountCharge error: Authority={Authority}", request.Authority);
return new VerifyWalletChargeResponse { Success = false, Message = ex.Message };
}
}
public override async Task<GetMagicWalletStatusResponse> GetMagicWalletStatus(
Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context)
{