feat: Update payment processing and callback mechanisms
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m22s
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:
+23
-22
@@ -97,43 +97,44 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. پیدا کردن UserOrder با PackageId
|
// 4. پیدا کردن آخرین خرید موفق پکیج
|
||||||
var packageOrder = await _context.UserOrders
|
var packagePurchase = await _context.UserPackagePurchases
|
||||||
.Include(o => o.Transaction)
|
.Include(p => p.Transaction)
|
||||||
.Where(o =>
|
.Where(p =>
|
||||||
o.UserId == user.Id &&
|
p.UserId == user.Id &&
|
||||||
o.PackageId != null &&
|
p.Transaction != null &&
|
||||||
o.PaymentStatus == PaymentStatus.Success)
|
p.Transaction.PaymentStatus == PaymentStatus.Success)
|
||||||
.OrderByDescending(o => o.Created)
|
.OrderByDescending(p => p.Created)
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
if (packageOrder == null)
|
if (packagePurchase == null)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
_logger.LogWarning(
|
||||||
"No successful package order found for UserId: {UserId}",
|
"No successful package purchase found for UserId: {UserId}",
|
||||||
request.UserId
|
request.UserId
|
||||||
);
|
);
|
||||||
throw new NotFoundException("سفارش پکیج یافت نشد");
|
throw new NotFoundException("سفارش پکیج یافت نشد");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. بررسی Transaction
|
// 5. بررسی Transaction
|
||||||
if (packageOrder.Transaction == null)
|
if (packagePurchase.Transaction == null)
|
||||||
{
|
{
|
||||||
_logger.LogError(
|
_logger.LogError(
|
||||||
"Transaction not found for OrderId: {OrderId}",
|
"Transaction not found for PurchaseId: {PurchaseId}",
|
||||||
packageOrder.Id
|
packagePurchase.Id
|
||||||
);
|
);
|
||||||
throw new NotFoundException("تراکنش مربوط به سفارش یافت نشد");
|
throw new NotFoundException("تراکنش مربوط به سفارش یافت نشد");
|
||||||
}
|
}
|
||||||
|
|
||||||
var transaction = packageOrder.Transaction;
|
var transaction = packagePurchase.Transaction;
|
||||||
|
|
||||||
if (transaction.Type != TransactionType.DepositIpg &&
|
if (transaction.Type != TransactionType.Buy &&
|
||||||
|
transaction.Type != TransactionType.DepositIpg &&
|
||||||
transaction.Type != TransactionType.DepositExternal1)
|
transaction.Type != TransactionType.DepositExternal1)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
_logger.LogWarning(
|
||||||
"Invalid transaction type for OrderId {OrderId}: {Type}",
|
"Invalid transaction type for PurchaseId {PurchaseId}: {Type}",
|
||||||
packageOrder.Id,
|
packagePurchase.Id,
|
||||||
transaction.Type
|
transaction.Type
|
||||||
);
|
);
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
@@ -141,9 +142,9 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5.5. بارگذاری پکیج از سفارش
|
// 5.5. بارگذاری پکیج از خرید
|
||||||
package = await _context.Packages
|
package = await _context.Packages
|
||||||
.FirstOrDefaultAsync(p => p.Id == packageOrder.PackageId && !p.IsDeleted, cancellationToken)
|
.FirstOrDefaultAsync(p => p.Id == packagePurchase.PackageId && !p.IsDeleted, cancellationToken)
|
||||||
?? throw new NotFoundException("پکیج یافت نشد");
|
?? throw new NotFoundException("پکیج یافت نشد");
|
||||||
|
|
||||||
// بررسی موجودی با مبلغ پکیج واقعی
|
// بررسی موجودی با مبلغ پکیج واقعی
|
||||||
@@ -194,11 +195,11 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
|||||||
|
|
||||||
if (isNewMembership)
|
if (isNewMembership)
|
||||||
{
|
{
|
||||||
// ایجاد عضویت جدید
|
// ایجاد عضویت جدید — IsActive = false تا زمان امضای قرارداد باشگاه
|
||||||
entity = new ClubMembership
|
entity = new ClubMembership
|
||||||
{
|
{
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
IsActive = true,
|
IsActive = false,
|
||||||
ActivatedAt = activationDate,
|
ActivatedAt = activationDate,
|
||||||
FirstActivationDate = activationDate,
|
FirstActivationDate = activationDate,
|
||||||
FirstPackageId = package.Id,
|
FirstPackageId = package.Id,
|
||||||
@@ -235,8 +236,8 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
|||||||
}
|
}
|
||||||
|
|
||||||
// فعالسازی مجدد یا خرید مجدد (Q19/Q20) — FirstActivation حفظ میشه (Q21), Last بروزرسانی میشه
|
// فعالسازی مجدد یا خرید مجدد (Q19/Q20) — FirstActivation حفظ میشه (Q21), Last بروزرسانی میشه
|
||||||
|
// مقدار IsActive قبلی حفظ میشه — اگر قبلاً فعال بوده، فعال بمونه
|
||||||
entity = existingMembership;
|
entity = existingMembership;
|
||||||
entity.IsActive = true;
|
|
||||||
entity.LastActivationDate = activationDate;
|
entity.LastActivationDate = activationDate;
|
||||||
entity.LastPackageId = package.Id;
|
entity.LastPackageId = package.Id;
|
||||||
entity.PurchaseMethod = user.PackagePurchaseMethod;
|
entity.PurchaseMethod = user.PackagePurchaseMethod;
|
||||||
|
|||||||
+3
-3
@@ -195,9 +195,9 @@ public class PlaceOrderCommandHandler : IRequestHandler<PlaceOrderCommand, Place
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// آدرس callback — زرینپال بعد از پرداخت کاربر را به اینجا هدایت میکند
|
// آدرس callback — زرینپال بعد از پرداخت مستقیم به فرانتآفیس هدایت میکند
|
||||||
var cmsBaseUrl = _configuration["CmsBaseUrl"] ?? "https://localhost:32846";
|
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
||||||
var callbackUrl = $"{cmsBaseUrl}/api/payment/discount-order/callback?orderId={order.Id}";
|
var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback?type=discount-order&orderId={order.Id}";
|
||||||
|
|
||||||
// درخواست به درگاه
|
// درخواست به درگاه
|
||||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
|
var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
|
||||||
|
|||||||
+3
-3
@@ -62,9 +62,9 @@ public class ChargeDiscountWalletCommandHandler
|
|||||||
throw new NotFoundException("کیف پول کاربر یافت نشد");
|
throw new NotFoundException("کیف پول کاربر یافت نشد");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. ایجاد درخواست پرداخت
|
// 3. ایجاد درخواست پرداخت — callback مستقیم به فرانتآفیس
|
||||||
var cmsBaseUrl = _configuration["CmsBaseUrl"] ?? "https://localhost:32846";
|
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
||||||
var callbackUrl = $"{cmsBaseUrl}/api/wallet/verify-discount-charge";
|
var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback?type=discount-wallet";
|
||||||
|
|
||||||
var paymentRequest = new PaymentRequest
|
var paymentRequest = new PaymentRequest
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-3
@@ -113,9 +113,9 @@ public class ChargeMagicWalletCommandHandler
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. ایجاد درخواست پرداخت
|
// 5. ایجاد درخواست پرداخت — callback مستقیم به فرانتآفیس
|
||||||
var cmsBaseUrl = _configuration["CmsBaseUrl"] ?? "https://localhost:32846";
|
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
||||||
var callbackUrl = $"{cmsBaseUrl}/api/wallet/verify-magic-charge";
|
var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback?type=magic-wallet";
|
||||||
|
|
||||||
var paymentRequest = new PaymentRequest
|
var paymentRequest = new PaymentRequest
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ namespace CMSMicroservice.Domain.Entities.Club;
|
|||||||
/// دوره خرید پکیج — هر بار خرید پکیج ۵۶M یک Cycle جدید ایجاد میشود.
|
/// دوره خرید پکیج — هر بار خرید پکیج ۵۶M یک Cycle جدید ایجاد میشود.
|
||||||
/// برای حل مشکل overwrite شدن ClubMembership.ActivatedAt در محاسبه کمیسیون.
|
/// برای حل مشکل overwrite شدن ClubMembership.ActivatedAt در محاسبه کمیسیون.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ClubMembershipCycle : BaseAuditableEntity, IHasHistory<History.ClubMembershipCycleHistory>
|
public class ClubMembershipCycle : BaseAuditableEntity
|
||||||
|
// , IHasHistory<History.ClubMembershipCycleHistory>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// شناسه کاربر
|
/// شناسه کاربر
|
||||||
@@ -83,20 +84,20 @@ public class ClubMembershipCycle : BaseAuditableEntity, IHasHistory<History.Club
|
|||||||
/// مقادیر New* از وضعیت فعلی پر میشوند.
|
/// مقادیر New* از وضعیت فعلی پر میشوند.
|
||||||
/// مقادیر Old* توسط HistoryTrackingSaveChangesInterceptor از OriginalValues پر میشوند.
|
/// مقادیر Old* توسط HistoryTrackingSaveChangesInterceptor از OriginalValues پر میشوند.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public History.ClubMembershipCycleHistory CreateHistorySnapshot(string action, string? performedBy)
|
// public History.ClubMembershipCycleHistory CreateHistorySnapshot(string action, string? performedBy)
|
||||||
{
|
// {
|
||||||
return new History.ClubMembershipCycleHistory
|
// return new History.ClubMembershipCycleHistory
|
||||||
{
|
// {
|
||||||
ClubMembershipCycleId = Id,
|
// ClubMembershipCycleId = Id,
|
||||||
UserId = UserId,
|
// UserId = UserId,
|
||||||
CycleNumber = CycleNumber,
|
// CycleNumber = CycleNumber,
|
||||||
NewIsCurrentCycle = IsCurrentCycle,
|
// NewIsCurrentCycle = IsCurrentCycle,
|
||||||
NewMagicStartedAt = MagicStartedAt,
|
// NewMagicStartedAt = MagicStartedAt,
|
||||||
NewMagicCompletedAt = MagicCompletedAt,
|
// NewMagicCompletedAt = MagicCompletedAt,
|
||||||
Action = Enum.TryParse<ClubMembershipCycleAction>(action, out var a)
|
// Action = Enum.TryParse<ClubMembershipCycleAction>(action, out var a)
|
||||||
? a
|
// ? a
|
||||||
: ClubMembershipCycleAction.ManualFix,
|
// : ClubMembershipCycleAction.ManualFix,
|
||||||
PerformedBy = performedBy
|
// PerformedBy = performedBy
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ namespace CMSMicroservice.Domain.Entities;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// پکیج — هر پکیج قیمت، ویژگیها و تنظیمات مستقل دارد
|
/// پکیج — هر پکیج قیمت، ویژگیها و تنظیمات مستقل دارد
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Package : BaseAuditableEntity, IHasHistory<History.PackageHistory>
|
public class Package : BaseAuditableEntity
|
||||||
|
// , IHasHistory<History.PackageHistory>
|
||||||
{
|
{
|
||||||
// === فیلدهای فعلی (حفظ) ===
|
// === فیلدهای فعلی (حفظ) ===
|
||||||
|
|
||||||
@@ -83,19 +84,19 @@ public class Package : BaseAuditableEntity, IHasHistory<History.PackageHistory>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// ساخت snapshot تاریخچه — Q27 auto-tracking
|
/// ساخت snapshot تاریخچه — Q27 auto-tracking
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public History.PackageHistory CreateHistorySnapshot(string action, string? performedBy)
|
// public History.PackageHistory CreateHistorySnapshot(string action, string? performedBy)
|
||||||
{
|
// {
|
||||||
return new History.PackageHistory
|
// return new History.PackageHistory
|
||||||
{
|
// {
|
||||||
PackageId = Id,
|
// PackageId = Id,
|
||||||
NewPrice = Price,
|
// NewPrice = Price,
|
||||||
NewActivationFee = ActivationFee,
|
// NewActivationFee = ActivationFee,
|
||||||
NewMagicMultiplier = MagicWalletMultiplier,
|
// NewMagicMultiplier = MagicWalletMultiplier,
|
||||||
NewMagicMaxDeposit = MagicWalletMaxDeposit,
|
// NewMagicMaxDeposit = MagicWalletMaxDeposit,
|
||||||
NewMaxBalancesPerLeg = MaxBalancesPerLeg,
|
// NewMaxBalancesPerLeg = MaxBalancesPerLeg,
|
||||||
NewIsActive = IsActive,
|
// NewIsActive = IsActive,
|
||||||
Action = Enum.TryParse<PackageAction>(action, out var a) ? a : PackageAction.Updated,
|
// Action = Enum.TryParse<PackageAction>(action, out var a) ? a : PackageAction.Updated,
|
||||||
PerformedBy = performedBy
|
// PerformedBy = performedBy
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.0.189</Version>
|
<Version>0.0.190</Version>
|
||||||
<DebugType>None</DebugType>
|
<DebugType>None</DebugType>
|
||||||
<DebugSymbols>False</DebugSymbols>
|
<DebugSymbols>False</DebugSymbols>
|
||||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ service DiscountOrderContract
|
|||||||
get: "/GetDiscountSalesReport"
|
get: "/GetDiscountSalesReport"
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Customer: Verify discount order payment after gateway callback
|
||||||
|
rpc CustomerVerifyDiscountOrderPayment(CustomerVerifyDiscountOrderPaymentRequest) returns (CustomerVerifyDiscountOrderPaymentResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/CustomerVerifyDiscountOrderPayment"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Place Order (Initial Step - Create Order)
|
// Place Order (Initial Step - Create Order)
|
||||||
@@ -309,3 +317,19 @@ message TopSellingProductDto
|
|||||||
int64 total_revenue = 5;
|
int64 total_revenue = 5;
|
||||||
int32 orders_count = 6;
|
int32 orders_count = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== Customer Verify Discount Order Payment =====
|
||||||
|
|
||||||
|
message CustomerVerifyDiscountOrderPaymentRequest
|
||||||
|
{
|
||||||
|
int64 order_id = 1;
|
||||||
|
string authority = 2;
|
||||||
|
string status = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CustomerVerifyDiscountOrderPaymentResponse
|
||||||
|
{
|
||||||
|
bool success = 1;
|
||||||
|
string message = 2;
|
||||||
|
int64 order_id = 3;
|
||||||
|
}
|
||||||
|
|||||||
@@ -89,6 +89,13 @@ service UserWalletContract
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
rpc VerifyMagicCharge(VerifyWalletChargeRequest) returns (VerifyWalletChargeResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/Customer/VerifyMagicCharge"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// ============= Discount Wallet Methods =============
|
// ============= Discount Wallet Methods =============
|
||||||
|
|
||||||
rpc InitiateDiscountCharge(InitiateDiscountChargeRequest) returns (InitiateDiscountChargeResponse){
|
rpc InitiateDiscountCharge(InitiateDiscountChargeRequest) returns (InitiateDiscountChargeResponse){
|
||||||
@@ -97,6 +104,12 @@ service UserWalletContract
|
|||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
rpc VerifyDiscountCharge(VerifyWalletChargeRequest) returns (VerifyWalletChargeResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/Customer/VerifyDiscountCharge"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
message CreateNewUserWalletRequest
|
message CreateNewUserWalletRequest
|
||||||
{
|
{
|
||||||
@@ -268,3 +281,17 @@ message InitiateDiscountChargeResponse
|
|||||||
string gateway_url = 2;
|
string gateway_url = 2;
|
||||||
string error_message = 3;
|
string error_message = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============= Wallet Verify Messages =============
|
||||||
|
|
||||||
|
message VerifyWalletChargeRequest
|
||||||
|
{
|
||||||
|
string authority = 1;
|
||||||
|
string status = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message VerifyWalletChargeResponse
|
||||||
|
{
|
||||||
|
bool success = 1;
|
||||||
|
string message = 2;
|
||||||
|
}
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
using CMSMicroservice.Application.Common.Interfaces;
|
|
||||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment;
|
|
||||||
using CMSMicroservice.Application.WalletCQ.Commands.VerifyDiscountWalletCharge;
|
|
||||||
using CMSMicroservice.Application.WalletCQ.Commands.VerifyMagicWalletCharge;
|
|
||||||
using MediatR;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Controllers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback endpoint for payment gateways (ZarinPal, etc.)
|
|
||||||
/// درگاه پرداخت بعد از پرداخت (یا لغو) کاربر را به اینجا redirect میکند
|
|
||||||
/// </summary>
|
|
||||||
[ApiController]
|
|
||||||
[AllowAnonymous] // کاربر از درگاه بانک برمیگردد — JWT ندارد
|
|
||||||
[ApiExplorerSettings(GroupName = "cms")]
|
|
||||||
public class PaymentCallbackController : ControllerBase
|
|
||||||
{
|
|
||||||
private readonly ISender _sender;
|
|
||||||
private readonly IPaymentGatewayService _paymentGateway;
|
|
||||||
private readonly IApplicationDbContext _context;
|
|
||||||
private readonly IConfiguration _configuration;
|
|
||||||
private readonly ILogger<PaymentCallbackController> _logger;
|
|
||||||
|
|
||||||
public PaymentCallbackController(
|
|
||||||
ISender sender,
|
|
||||||
IPaymentGatewayService paymentGateway,
|
|
||||||
IApplicationDbContext context,
|
|
||||||
IConfiguration configuration,
|
|
||||||
ILogger<PaymentCallbackController> logger)
|
|
||||||
{
|
|
||||||
_sender = sender;
|
|
||||||
_paymentGateway = paymentGateway;
|
|
||||||
_context = context;
|
|
||||||
_configuration = configuration;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback برای پرداخت سفارش فروشگاه تخفیفی
|
|
||||||
/// زرینپال کاربر را با Authority و Status به این endpoint برمیگرداند
|
|
||||||
/// </summary>
|
|
||||||
[HttpGet("/api/payment/discount-order/callback")]
|
|
||||||
public async Task<IActionResult> DiscountOrderCallback(
|
|
||||||
[FromQuery] long orderId,
|
|
||||||
[FromQuery(Name = "Authority")] string? authority,
|
|
||||||
[FromQuery(Name = "Status")] string? status,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"Payment callback received: OrderId={OrderId}, Authority={Authority}, Status={Status}",
|
|
||||||
orderId, authority, status);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// پیدا کردن سفارش و تراکنش
|
|
||||||
var order = await _context.DiscountOrders
|
|
||||||
.Include(o => o.OrderDetails)
|
|
||||||
.FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
|
|
||||||
|
|
||||||
if (order == null)
|
|
||||||
{
|
|
||||||
_logger.LogError("Payment callback: Order #{OrderId} not found", orderId);
|
|
||||||
return Redirect($"{frontOfficeBaseUrl}/discount-store/orders?error=order-not-found");
|
|
||||||
}
|
|
||||||
|
|
||||||
var transaction = order.TransactionId.HasValue
|
|
||||||
? await _context.Transactions.FirstOrDefaultAsync(
|
|
||||||
t => t.Id == order.TransactionId.Value, cancellationToken)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// تأیید پرداخت از درگاه
|
|
||||||
bool paymentSuccess = false;
|
|
||||||
string? refId = null;
|
|
||||||
|
|
||||||
if (string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& !string.IsNullOrEmpty(authority))
|
|
||||||
{
|
|
||||||
// Verify با مبلغ از دیتابیس (تومان — تبدیل به ریال در ZarinPalService)
|
|
||||||
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
|
||||||
authority,
|
|
||||||
status!,
|
|
||||||
order.GatewayAmountPaid, // مبلغ به تومان
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
paymentSuccess = verifyResult.IsSuccess;
|
|
||||||
refId = verifyResult.TrackingCode ?? verifyResult.RefId;
|
|
||||||
|
|
||||||
// آپدیت PaymentTransaction با نتیجه verify
|
|
||||||
var paymentTx = await _context.PaymentTransactions
|
|
||||||
.FirstOrDefaultAsync(pt => pt.Authority == authority, 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(cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"Payment verification for Order #{OrderId}: Success={Success}, RefId={RefId}, Message={Message}",
|
|
||||||
orderId, paymentSuccess, refId, verifyResult.Message);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Payment cancelled by user for Order #{OrderId}", orderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// تکمیل سفارش از طریق CQRS
|
|
||||||
var completeResult = await _sender.Send(new CompleteOrderPaymentCommand
|
|
||||||
{
|
|
||||||
OrderId = orderId,
|
|
||||||
TransactionId = transaction?.Id ?? 0,
|
|
||||||
PaymentSuccess = paymentSuccess,
|
|
||||||
RefId = refId
|
|
||||||
}, cancellationToken);
|
|
||||||
|
|
||||||
// Redirect به FrontOffice
|
|
||||||
if (paymentSuccess && completeResult.Success)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Payment completed successfully for Order #{OrderId}", orderId);
|
|
||||||
return Redirect(
|
|
||||||
$"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=success");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Payment failed for Order #{OrderId}", orderId);
|
|
||||||
return Redirect(
|
|
||||||
$"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Payment callback error for Order #{OrderId}", orderId);
|
|
||||||
return Redirect(
|
|
||||||
$"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback برای شارژ کیفپول جادویی — زرینپال بعد از پرداخت کاربر را اینجا برمیگرداند
|
|
||||||
/// </summary>
|
|
||||||
[HttpGet("/api/wallet/verify-magic-charge")]
|
|
||||||
public async Task<IActionResult> MagicChargeCallback(
|
|
||||||
[FromQuery(Name = "Authority")] string? authority,
|
|
||||||
[FromQuery(Name = "Status")] string? status,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"Magic charge callback received: Authority={Authority}, Status={Status}",
|
|
||||||
authority, status);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(authority))
|
|
||||||
{
|
|
||||||
_logger.LogError("Magic charge callback: Authority is missing");
|
|
||||||
return Redirect($"{frontOfficeBaseUrl}/profile/magic-wallet?payment=error&reason=no-authority");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _sender.Send(new VerifyMagicWalletChargeCommand
|
|
||||||
{
|
|
||||||
Authority = authority,
|
|
||||||
Status = status ?? "NOK"
|
|
||||||
}, cancellationToken);
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"Magic charge completed successfully. Authority={Authority}",
|
|
||||||
authority);
|
|
||||||
|
|
||||||
return Redirect($"{frontOfficeBaseUrl}/profile/magic-wallet?payment=success");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Magic charge callback error. Authority={Authority}", authority);
|
|
||||||
return Redirect($"{frontOfficeBaseUrl}/profile/magic-wallet?payment=failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback برای شارژ کیفپول تخفیفی — زرینپال بعد از پرداخت کاربر را اینجا برمیگرداند
|
|
||||||
/// </summary>
|
|
||||||
[HttpGet("/api/wallet/verify-discount-charge")]
|
|
||||||
public async Task<IActionResult> DiscountChargeCallback(
|
|
||||||
[FromQuery(Name = "Authority")] string? authority,
|
|
||||||
[FromQuery(Name = "Status")] string? status,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"Discount charge callback received: Authority={Authority}, Status={Status}",
|
|
||||||
authority, status);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(authority))
|
|
||||||
{
|
|
||||||
_logger.LogError("Discount charge callback: Authority is missing");
|
|
||||||
return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=error&reason=no-authority");
|
|
||||||
}
|
|
||||||
|
|
||||||
// پیدا کردن PaymentTransaction برای استخراج UserId و Amount
|
|
||||||
var paymentTx = await _context.PaymentTransactions
|
|
||||||
.FirstOrDefaultAsync(pt => pt.Authority == authority, cancellationToken);
|
|
||||||
|
|
||||||
if (paymentTx == null || !paymentTx.UserId.HasValue)
|
|
||||||
{
|
|
||||||
_logger.LogError("Discount charge callback: PaymentTransaction not found for Authority={Authority}", authority);
|
|
||||||
return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=error&reason=tx-not-found");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Discount charge cancelled by user. Authority={Authority}", authority);
|
|
||||||
return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=cancelled");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _sender.Send(new VerifyDiscountWalletChargeCommand
|
|
||||||
{
|
|
||||||
UserId = paymentTx.UserId.Value,
|
|
||||||
Amount = paymentTx.Amount,
|
|
||||||
Authority = authority
|
|
||||||
}, cancellationToken);
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"Discount charge completed successfully. Authority={Authority}, UserId={UserId}",
|
|
||||||
authority, paymentTx.UserId.Value);
|
|
||||||
|
|
||||||
return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=success");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Discount charge callback error. Authority={Authority}", authority);
|
|
||||||
return Redirect($"{frontOfficeBaseUrl}/profile/charge-discount-wallet?payment=failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,8 @@ using Grpc.Core;
|
|||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
|
|
||||||
@@ -21,15 +23,24 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
|||||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||||
private readonly ISender _sender;
|
private readonly ISender _sender;
|
||||||
private readonly ICurrentUserService _currentUserService;
|
private readonly ICurrentUserService _currentUserService;
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly IPaymentGatewayService _paymentGateway;
|
||||||
|
private readonly ILogger<DiscountOrderService> _logger;
|
||||||
|
|
||||||
public DiscountOrderService(
|
public DiscountOrderService(
|
||||||
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
||||||
ISender sender,
|
ISender sender,
|
||||||
ICurrentUserService currentUserService)
|
ICurrentUserService currentUserService,
|
||||||
|
IApplicationDbContext context,
|
||||||
|
IPaymentGatewayService paymentGateway,
|
||||||
|
ILogger<DiscountOrderService> logger)
|
||||||
{
|
{
|
||||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||||
_sender = sender;
|
_sender = sender;
|
||||||
_currentUserService = currentUserService;
|
_currentUserService = currentUserService;
|
||||||
|
_context = context;
|
||||||
|
_paymentGateway = paymentGateway;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long GetCurrentUserId()
|
private long GetCurrentUserId()
|
||||||
@@ -174,6 +185,108 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
|||||||
return await _dispatchRequestToCQRS.Handle<GetDiscountSalesReportRequest, GetDiscountSalesReportQuery, GetDiscountSalesReportResponse>(request, context);
|
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
|
private static CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus MapPaymentStatus(DomainEnums.PaymentStatus status) => status switch
|
||||||
{
|
{
|
||||||
DomainEnums.PaymentStatus.Success => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentCompleted,
|
DomainEnums.PaymentStatus.Success => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentCompleted,
|
||||||
|
|||||||
@@ -351,6 +351,24 @@ public class PackageService : PackageContract.PackageContractBase
|
|||||||
|
|
||||||
await _context.SaveChangesAsync(context.CancellationToken);
|
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
|
return new CustomerVerifyPackagePurchaseResponse
|
||||||
{
|
{
|
||||||
Success = verifyResult.IsSuccess,
|
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)
|
public override async Task<CalculateOrderPVResponse> CalculateOrderPV(CalculateOrderPVRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
var order = await _context.UserOrders
|
var order = await _context.UserOrders
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public class UserService : UserContract.UserContractBase
|
|||||||
private readonly ICurrentUserService _currentUserService;
|
private readonly ICurrentUserService _currentUserService;
|
||||||
private readonly IHashService _hashService;
|
private readonly IHashService _hashService;
|
||||||
private readonly CMSMicroservice.Application.Common.FileManager.IFileManager _fileManager;
|
private readonly CMSMicroservice.Application.Common.FileManager.IFileManager _fileManager;
|
||||||
|
private readonly IGenerateJwtToken _generateJwt;
|
||||||
|
|
||||||
public UserService(
|
public UserService(
|
||||||
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
||||||
@@ -42,7 +43,8 @@ public class UserService : UserContract.UserContractBase
|
|||||||
IApplicationDbContext context,
|
IApplicationDbContext context,
|
||||||
ICurrentUserService currentUserService,
|
ICurrentUserService currentUserService,
|
||||||
IHashService hashService,
|
IHashService hashService,
|
||||||
CMSMicroservice.Application.Common.FileManager.IFileManager fileManager)
|
CMSMicroservice.Application.Common.FileManager.IFileManager fileManager,
|
||||||
|
IGenerateJwtToken generateJwt)
|
||||||
{
|
{
|
||||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||||
_sender = sender;
|
_sender = sender;
|
||||||
@@ -50,6 +52,7 @@ public class UserService : UserContract.UserContractBase
|
|||||||
_currentUserService = currentUserService;
|
_currentUserService = currentUserService;
|
||||||
_hashService = hashService;
|
_hashService = hashService;
|
||||||
_fileManager = fileManager;
|
_fileManager = fileManager;
|
||||||
|
_generateJwt = generateJwt;
|
||||||
}
|
}
|
||||||
public override async Task<CreateNewUserResponse> CreateNewUser(CreateNewUserRequest request, ServerCallContext context)
|
public override async Task<CreateNewUserResponse> CreateNewUser(CreateNewUserRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
@@ -115,12 +118,20 @@ public class UserService : UserContract.UserContractBase
|
|||||||
|
|
||||||
var user = await _context.Users
|
var user = await _context.Users
|
||||||
.AsNoTracking()
|
.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)
|
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||||
.FirstOrDefaultAsync(context.CancellationToken);
|
.FirstOrDefaultAsync(context.CancellationToken);
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
|
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
|
||||||
|
|
||||||
|
// تولید توکن JWT با آخرین اطلاعات کاربر
|
||||||
|
var token = await _generateJwt.GenerateJwtToken(user);
|
||||||
|
|
||||||
return new GetUserForCustomerResponse
|
return new GetUserForCustomerResponse
|
||||||
{
|
{
|
||||||
Id = user.Id,
|
Id = user.Id,
|
||||||
@@ -141,7 +152,8 @@ public class UserService : UserContract.UserContractBase
|
|||||||
PushNotifications = user.PushNotifications,
|
PushNotifications = user.PushNotifications,
|
||||||
BirthDate = user.BirthDate.HasValue
|
BirthDate = user.BirthDate.HasValue
|
||||||
? Timestamp.FromDateTime(DateTime.SpecifyKind(user.BirthDate.Value, DateTimeKind.Utc))
|
? 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.UserWalletCQ.Commands.DeleteUserWallet;
|
||||||
using CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet;
|
using CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet;
|
||||||
using CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet;
|
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.GetUserWallet;
|
||||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter;
|
using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter;
|
||||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory;
|
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory;
|
||||||
@@ -16,6 +18,7 @@ using CMSMicroservice.Domain.Enums;
|
|||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
@@ -25,17 +28,20 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
|
|||||||
private readonly ISender _sender;
|
private readonly ISender _sender;
|
||||||
private readonly IApplicationDbContext _context;
|
private readonly IApplicationDbContext _context;
|
||||||
private readonly ICurrentUserService _currentUserService;
|
private readonly ICurrentUserService _currentUserService;
|
||||||
|
private readonly ILogger<UserWalletService> _logger;
|
||||||
|
|
||||||
public UserWalletService(
|
public UserWalletService(
|
||||||
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
||||||
ISender sender,
|
ISender sender,
|
||||||
IApplicationDbContext context,
|
IApplicationDbContext context,
|
||||||
ICurrentUserService currentUserService)
|
ICurrentUserService currentUserService,
|
||||||
|
ILogger<UserWalletService> logger)
|
||||||
{
|
{
|
||||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||||
_sender = sender;
|
_sender = sender;
|
||||||
_context = context;
|
_context = context;
|
||||||
_currentUserService = currentUserService;
|
_currentUserService = currentUserService;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
public override async Task<CreateNewUserWalletResponse> CreateNewUserWallet(CreateNewUserWalletRequest request, ServerCallContext context)
|
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(
|
public override async Task<GetMagicWalletStatusResponse> GetMagicWalletStatus(
|
||||||
Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context)
|
Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"CmsBaseUrl": "https://localhost:32846",
|
|
||||||
"FrontOfficeBaseUrl": "http://localhost:5268",
|
|
||||||
"ZarinPal": {
|
|
||||||
"UseSandbox": true
|
|
||||||
},
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Debug",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
|
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
|
||||||
"UseSandbox": true
|
"UseSandbox": true
|
||||||
},
|
},
|
||||||
"CmsBaseUrl": "https://cms.se.kbs1.ir",
|
"CmsBaseUrl": "https://localhost:32846",
|
||||||
"FrontOfficeBaseUrl": "https://foursat.se.kbs1.ir",
|
"FrontOfficeBaseUrl": "http://localhost:5268",
|
||||||
"FMS": {
|
"FMS": {
|
||||||
"Address": "https://dl.afrino.co"
|
"Address": "https://dl.afrino.co"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user