Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 8m48s
Build and Deploy to Production / build-and-deploy (push) Successful in 8m48s
# Conflicts: # .gitea/workflows/kub-deploy.yml # k8s/staging/cms-deployment.yaml # src/CMSMicroservice.Application/DiscountShopCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs # src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs # src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs # src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs # src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs # src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs # src/CMSMicroservice.WebApi/appsettings.Production.json # src/CMSMicroservice.WebApi/appsettings.Staging.json # src/CMSMicroservice.WebApi/appsettings.json
This commit is contained in:
@@ -68,7 +68,9 @@ public class CommissionProfile : IRegister
|
||||
.Map(dest => dest.WithdrawnAt, src => src.WithdrawnAt.HasValue
|
||||
? Timestamp.FromDateTime(src.WithdrawnAt.Value.ToUniversalTime())
|
||||
: null)
|
||||
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created));
|
||||
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created))
|
||||
.Map(dest => dest.PackageId, src => src.PackageId)
|
||||
.Map(dest => dest.PackageTitle, src => src.PackageTitle);
|
||||
|
||||
// GetWeekDefinitions Request Mapping
|
||||
config.NewConfig<GetWeekDefinitionsRequest, GetWeekDefinitionsQuery>()
|
||||
@@ -127,7 +129,9 @@ public class CommissionProfile : IRegister
|
||||
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.CalculatedAt.Value, DateTimeKind.Utc))
|
||||
: null)
|
||||
.Map(dest => dest.IsExpired, src => src.IsExpired)
|
||||
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created));
|
||||
.Map(dest => dest.Created, src => Timestamp.FromDateTimeOffset(src.Created))
|
||||
.Map(dest => dest.PackageId, src => src.PackageId)
|
||||
.Map(dest => dest.PackageTitle, src => src.PackageTitle);
|
||||
|
||||
// GetMyCommissionPayouts Request Mapping
|
||||
config.NewConfig<GetMyCommissionPayoutsRequest, GetMyCommissionPayoutsQuery>()
|
||||
@@ -135,6 +139,7 @@ public class CommissionProfile : IRegister
|
||||
? (CommissionPayoutStatus?)src.Status.Value
|
||||
: null)
|
||||
.Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId != null ? src.WeekDefinitionId.Value : (long?)null)
|
||||
.Map(dest => dest.PackageId, src => src.PackageId != null ? src.PackageId.Value : (long?)null)
|
||||
.Map(dest => dest.PaginationState, src => new PaginationState
|
||||
{
|
||||
PageNumber = src.PageNumber > 0 ? src.PageNumber : 1,
|
||||
@@ -160,12 +165,15 @@ public class CommissionProfile : IRegister
|
||||
.Map(dest => dest.CalculatedDate, src => src.CalculatedDate.HasValue
|
||||
? Timestamp.FromDateTime(src.CalculatedDate.Value.ToUniversalTime())
|
||||
: null)
|
||||
.Map(dest => dest.DatePersian, src => src.DatePersian);
|
||||
.Map(dest => dest.DatePersian, src => src.DatePersian)
|
||||
.Map(dest => dest.PackageId, src => src.PackageId)
|
||||
.Map(dest => dest.PackageTitle, src => src.PackageTitle);
|
||||
|
||||
// GetMyWeeklyBalances Request Mapping
|
||||
config.NewConfig<GetMyWeeklyBalancesRequest, GetMyWeeklyBalancesQuery>()
|
||||
.Map(dest => dest.WeekDefinitionId, src => src.WeekDefinitionId != null ? src.WeekDefinitionId.Value : (long?)null)
|
||||
.Map(dest => dest.OnlyActive, src => src.OnlyActive)
|
||||
.Map(dest => dest.PackageId, src => src.PackageId != null ? src.PackageId.Value : (long?)null)
|
||||
.Map(dest => dest.PaginationState, src => new PaginationState
|
||||
{
|
||||
PageNumber = src.PageNumber > 0 ? src.PageNumber : 1,
|
||||
@@ -199,6 +207,8 @@ public class CommissionProfile : IRegister
|
||||
.Map(dest => dest.IsExpired, src => src.IsExpired)
|
||||
.Map(dest => dest.CalculatedAt, src => src.CalculatedAt.HasValue
|
||||
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.CalculatedAt.Value, DateTimeKind.Utc))
|
||||
: null);
|
||||
: null)
|
||||
.Map(dest => dest.PackageId, src => src.PackageId)
|
||||
.Map(dest => dest.PackageTitle, src => src.PackageTitle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class NetworkMembershipProfile : IRegister
|
||||
.Map(dest => dest.MaxNetworkDepth, src => src.MaxNetworkDepth)
|
||||
.Map(dest => dest.HasReceivedDayaCredit, src => src.HasReceivedDayaCredit)
|
||||
.Map(dest => dest.PackagePurchaseMethod, src => (int)src.PackagePurchaseMethod)
|
||||
.Map(dest => dest.HasPurchasedGoldenPackage, src => src.HasPurchasedGoldenPackage)
|
||||
.Map(dest => dest.HasPurchasedPackage, src => src.HasPurchasedPackage)
|
||||
.Map(dest => dest.TotalEarnedCommission, src => (double)src.TotalEarnedCommission)
|
||||
.Map(dest => dest.TotalPaidCommission, src => (double)src.TotalPaidCommission)
|
||||
.Map(dest => dest.PendingCommission, src => (double)src.PendingCommission)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
|
||||
using CMSMicroservice.Protobuf.Protos.Package;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
@@ -12,33 +8,6 @@ public class PackageProfile : IRegister
|
||||
{
|
||||
void IRegister.Register(TypeAdapterConfig config)
|
||||
{
|
||||
// PurchaseGoldenPackage
|
||||
config.NewConfig<PurchaseGoldenPackageRequest, PurchaseGoldenPackageCommand>()
|
||||
.Map(dest => dest.UserId, src => src.UserId)
|
||||
.Map(dest => dest.PackageId, src => src.PackageId)
|
||||
.Map(dest => dest.ReturnUrl, src => src.ReturnUrl);
|
||||
|
||||
config.NewConfig<PurchaseGoldenPackageResponseDto, PurchaseGoldenPackageResponse>()
|
||||
.Map(dest => dest.Success, src => src.Success)
|
||||
.Map(dest => dest.Message, src => src.Message)
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.PaymentGatewayUrl, src => src.PaymentGatewayUrl)
|
||||
.Map(dest => dest.TrackingCode, src => src.TrackingCode);
|
||||
|
||||
// VerifyGoldenPackagePurchase
|
||||
config.NewConfig<VerifyGoldenPackagePurchaseRequest, VerifyGoldenPackagePurchaseCommand>()
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.Authority, src => src.Authority)
|
||||
.Map(dest => dest.Status, src => src.Status);
|
||||
|
||||
config.NewConfig<VerifyGoldenPackagePurchaseResponseDto, VerifyGoldenPackagePurchaseResponse>()
|
||||
.Map(dest => dest.Success, src => src.Success)
|
||||
.Map(dest => dest.Message, src => src.Message)
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.TransactionId, src => src.TransactionId)
|
||||
.Map(dest => dest.ReferenceCode, src => src.ReferenceCode)
|
||||
.Map(dest => dest.WalletBalance, src => src.WalletBalance);
|
||||
|
||||
// GetUserPackageStatus
|
||||
config.NewConfig<GetUserPackageStatusRequest, GetUserPackageStatusQuery>()
|
||||
.Map(dest => dest.UserId, src => src.UserId);
|
||||
@@ -55,34 +24,6 @@ public class PackageProfile : IRegister
|
||||
.Map(dest => dest.LastPurchaseDate, src => src.LastPurchaseDate.HasValue
|
||||
? Timestamp.FromDateTime(src.LastPurchaseDate.Value.ToUniversalTime())
|
||||
: null);
|
||||
|
||||
// InitiateBasePackagePayment (56M پکیج پایه)
|
||||
config.NewConfig<InitiateBasePackagePaymentRequest, InitiateBasePackagePaymentCommand>()
|
||||
.Map(dest => dest.UserId, src => src.UserId);
|
||||
|
||||
config.NewConfig<InitiateBasePackagePaymentResponseDto, InitiateBasePackagePaymentResponse>()
|
||||
.Map(dest => dest.Success, src => src.Success)
|
||||
.Map(dest => dest.Message, src => src.Message)
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.TransactionId, src => src.TransactionId)
|
||||
.Map(dest => dest.Amount, src => src.Amount);
|
||||
|
||||
// VerifyBasePackagePayment (تأیید پرداخت پکیج پایه)
|
||||
config.NewConfig<VerifyBasePackagePaymentRequest, VerifyBasePackagePaymentCommand>()
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.TransactionId, src => src.TransactionId)
|
||||
.Map(dest => dest.PaymentSuccess, src => src.PaymentSuccess)
|
||||
.Map(dest => dest.RefId, src => src.RefId)
|
||||
.Map(dest => dest.Message, src => src.Message);
|
||||
|
||||
config.NewConfig<VerifyBasePackagePaymentResponseDto, VerifyBasePackagePaymentResponse>()
|
||||
.Map(dest => dest.Success, src => src.Success)
|
||||
.Map(dest => dest.Message, src => src.Message)
|
||||
.Map(dest => dest.OrderId, src => src.OrderId)
|
||||
.Map(dest => dest.TransactionId, src => src.TransactionId)
|
||||
.Map(dest => dest.ReferenceCode, src => src.ReferenceCode)
|
||||
.Map(dest => dest.WalletBalance, src => src.WalletBalance)
|
||||
.Map(dest => dest.DiscountBalance, src => src.DiscountBalance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
namespace CMSMicroservice.WebApi.Common.Mappings;
|
||||
|
||||
public class UserWalletChangeLogProfile : IRegister
|
||||
public class UserWalletHistoryProfile : IRegister
|
||||
{
|
||||
void IRegister.Register(TypeAdapterConfig config)
|
||||
{
|
||||
@@ -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 با مبلغ از دیتابیس (ریال)
|
||||
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}/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}/magic-wallet?payment=success");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Magic charge callback error. Authority={Authority}", authority);
|
||||
return Redirect($"{frontOfficeBaseUrl}/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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,7 +281,7 @@ static bool IsCustomerService(string serviceName)
|
||||
"User", "UserAddress", "Commission", "NetworkMembership",
|
||||
"ClubMembership", "UserOrder", "UserWallet", "UserCarts",
|
||||
"DiscountShoppingCart", "City", "Package", "Transactions",
|
||||
"UserContract", "UserWalletChangeLog"
|
||||
"UserContract", "UserWalletHistory"
|
||||
};
|
||||
|
||||
return customerServices.Any(customer => serviceName.Contains(customer, StringComparison.OrdinalIgnoreCase)) &&
|
||||
|
||||
@@ -98,8 +98,11 @@ public class CategoryService : CategoryContract.CategoryContractBase
|
||||
return response;
|
||||
}
|
||||
|
||||
#region [ARCHIVED] GetCategoryByIdForCustomer — تکراری: GetCategory (admin) + GetAllCategoriesForCustomer کافی است
|
||||
[Obsolete("ARCHIVED: Use GetCategory or GetAllCategoriesForCustomer instead")]
|
||||
public override async Task<GetCategoryByIdForCustomerResponse> GetCategoryByIdForCustomer(GetCategoryByIdForCustomerRequest request, ServerCallContext context)
|
||||
{
|
||||
// [ARCHIVED] duplicate — GetCategory + GetAllCategoriesForCustomer cover this
|
||||
var query = new GetCategoryQuery { Id = request.Id };
|
||||
var result = await _sender.Send(query, context.CancellationToken);
|
||||
|
||||
@@ -118,4 +121,5 @@ public class CategoryService : CategoryContract.CategoryContractBase
|
||||
}
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -177,6 +177,8 @@ public class CityService : CityContract.CityContractBase
|
||||
};
|
||||
}
|
||||
|
||||
#region [ARCHIVED] UpdateCity & DeleteCity — شهرها seed data هستند، ویرایش/حذف دستی باعث خرابی آدرسها میشود
|
||||
[Obsolete("ARCHIVED: Cities are seed data — manual edit/delete breaks user addresses")]
|
||||
public override async Task<Empty> UpdateCity(
|
||||
UpdateCityRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -195,6 +197,7 @@ public class CityService : CityContract.CityContractBase
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
[Obsolete("ARCHIVED: Cities are seed data — manual delete breaks user addresses")]
|
||||
public override async Task<Empty> DeleteCity(
|
||||
DeleteCityRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -206,6 +209,7 @@ public class CityService : CityContract.CityContractBase
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return new Empty();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -2,37 +2,42 @@ using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures;
|
||||
using CMSMicroservice.Application.Common.Authorization;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Common;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Protobuf.Protos.Configuration;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Grpc.Core;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس تنظیمات سیستم - خواندن از SystemConstants
|
||||
/// سرویس تنظیمات سیستم — مقادیر پکیجی از Package entity خوانده میشوند
|
||||
/// </summary>
|
||||
public class ConfigurationService : ConfigurationContract.ConfigurationContractBase
|
||||
{
|
||||
private readonly ILogger<ConfigurationService> _logger;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public ConfigurationService(
|
||||
ILogger<ConfigurationService> logger,
|
||||
ICurrentUserService currentUserService,
|
||||
IMediator mediator)
|
||||
IMediator mediator,
|
||||
IApplicationDbContext context)
|
||||
{
|
||||
_logger = logger;
|
||||
_currentUserService = currentUserService;
|
||||
_mediator = mediator;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تنظیمات با کلید خاص
|
||||
/// </summary>
|
||||
public override Task<GetConfigurationByKeyResponse> GetConfigurationByKey(GetConfigurationByKeyRequest request, ServerCallContext context)
|
||||
public override async Task<GetConfigurationByKeyResponse> GetConfigurationByKey(GetConfigurationByKeyRequest request, ServerCallContext context)
|
||||
{
|
||||
var response = new GetConfigurationByKeyResponse
|
||||
{
|
||||
@@ -43,30 +48,33 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
LastModified = Timestamp.FromDateTime(DateTime.UtcNow)
|
||||
};
|
||||
|
||||
// خواندن مقدار از SystemConstants بر اساس کلید
|
||||
response.Value = GetConfigurationValue(request.Key);
|
||||
// برای کلیدهای پکیجی، از دیتابیس میخوانیم
|
||||
var package = await GetBasePackageAsync(context.CancellationToken);
|
||||
response.Value = GetConfigurationValue(request.Key, package);
|
||||
response.Description = GetConfigurationDescription(request.Key);
|
||||
|
||||
_logger.LogDebug("Configuration requested: Key={Key}, Value={Value}", request.Key, response.Value);
|
||||
|
||||
return Task.FromResult(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تنظیمات باشگاه مشتریان
|
||||
/// </summary>
|
||||
public override Task<GetClubConfigurationResponse> GetClubConfiguration(Empty request, ServerCallContext context)
|
||||
public override async Task<GetClubConfigurationResponse> GetClubConfiguration(Empty request, ServerCallContext context)
|
||||
{
|
||||
var package = await GetBasePackageAsync(context.CancellationToken);
|
||||
|
||||
var response = new GetClubConfigurationResponse
|
||||
{
|
||||
ActivationFee = SystemConstants.ClubActivationFee,
|
||||
MembershipGiftValue = SystemConstants.ClubMembershipGiftValue
|
||||
ActivationFee = package.ActivationFee,
|
||||
MembershipGiftValue = package.ActivationFee
|
||||
};
|
||||
|
||||
_logger.LogDebug("Club configuration requested: ActivationFee={ActivationFee}, GiftValue={GiftValue}",
|
||||
response.ActivationFee, response.MembershipGiftValue);
|
||||
|
||||
return Task.FromResult(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -111,18 +119,19 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
/// دریافت تمام تنظیمات
|
||||
/// </summary>
|
||||
[RequiresPermission(PermissionNames.SettingsView)]
|
||||
public override Task<GetAllConfigurationsResponse> GetAllConfigurations(GetAllConfigurationsRequest request, ServerCallContext context)
|
||||
public override async Task<GetAllConfigurationsResponse> GetAllConfigurations(GetAllConfigurationsRequest request, ServerCallContext context)
|
||||
{
|
||||
var package = await GetBasePackageAsync(context.CancellationToken);
|
||||
var response = new GetAllConfigurationsResponse();
|
||||
|
||||
// Club Settings
|
||||
response.Models.Add(CreateConfigModel("Club.ActivationFee", SystemConstants.ClubActivationFee.ToString(), "هزینه فعالسازی عضویت باشگاه", 2));
|
||||
response.Models.Add(CreateConfigModel("Club.MembershipGiftValue", SystemConstants.ClubMembershipGiftValue.ToString(), "مبلغ هدیه حق عضویت باشگاه", 2));
|
||||
// Club Settings (از Package)
|
||||
response.Models.Add(CreateConfigModel("Club.ActivationFee", package.ActivationFee.ToString(), "هزینه فعالسازی عضویت باشگاه", 2));
|
||||
response.Models.Add(CreateConfigModel("Club.MembershipGiftValue", package.ActivationFee.ToString(), "مبلغ هدیه حق عضویت باشگاه", 2));
|
||||
|
||||
// Commission Settings
|
||||
// Commission Settings (سقفها از Package)
|
||||
response.Models.Add(CreateConfigModel("Commission.MinWithdrawalAmount", SystemConstants.CommissionMinWithdrawalAmount.ToString(), "حداقل مبلغ برداشت", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.MaxWeeklyBalancesPerLeg", SystemConstants.CommissionMaxWeeklyBalancesPerLeg.ToString(), "سقف تعادل هفتگی برای هر دست", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.MaxNetworkLevel", SystemConstants.CommissionMaxNetworkLevel.ToString(), "حداکثر عمق شبکه برای محاسبه کمیسیون", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.MaxWeeklyBalancesPerLeg", package.MaxBalancesPerLeg.ToString(), "سقف تعادل هفتگی برای هر دست", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.MaxNetworkLevel", package.MaxNetworkLevel.ToString(), "حداکثر عمق شبکه برای محاسبه کمیسیون", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.CashWithdrawalEnabled", SystemConstants.CommissionCashWithdrawalEnabled.ToString(), "امکان برداشت نقدی", 3));
|
||||
response.Models.Add(CreateConfigModel("Commission.CalculationStrategy", SystemConstants.CommissionCalculationStrategy, "روش محاسبه کمیسیون", 3));
|
||||
|
||||
@@ -130,9 +139,14 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
response.Models.Add(CreateConfigModel("Network.AllowOrphanNodes", SystemConstants.NetworkAllowOrphanNodes.ToString(), "اجازه حذف والدین با فرزند", 1));
|
||||
response.Models.Add(CreateConfigModel("Network.MaxChildrenPerLeg", SystemConstants.NetworkMaxChildrenPerLeg.ToString(), "حداکثر تعداد فرزند مستقیم در هر پا", 1));
|
||||
|
||||
// Package Settings
|
||||
response.Models.Add(CreateConfigModel("Package.BasePackageAmount", SystemConstants.BasePackageAmount.ToString(), "مبلغ پکیج پایه", 0));
|
||||
response.Models.Add(CreateConfigModel("Package.DayaLoanAmount", SystemConstants.DayaLoanAmount.ToString(), "مبلغ وام دایا", 0));
|
||||
// Package Settings (از Package)
|
||||
response.Models.Add(CreateConfigModel("Package.BasePackageAmount", package.Price.ToString(), "مبلغ پکیج پایه", 0));
|
||||
response.Models.Add(CreateConfigModel("Package.DayaLoanAmount", package.Price.ToString(), "مبلغ وام دایا", 0));
|
||||
|
||||
// Magic Wallet Settings (از Package)
|
||||
response.Models.Add(CreateConfigModel("MagicWallet.Multiplier", package.MagicWalletMultiplier.ToString(), "ضریب شارژ کیفپول جادویی", 0));
|
||||
response.Models.Add(CreateConfigModel("MagicWallet.MaxDeposit", package.MagicWalletMaxDeposit.ToString(), "سقف واریز هر دور جادویی", 0));
|
||||
response.Models.Add(CreateConfigModel("MagicWallet.MaxCredit", package.MagicWalletMaxCredit.ToString(), "سقف اعتبار هر دور جادویی", 0));
|
||||
|
||||
// System Settings
|
||||
response.Models.Add(CreateConfigModel("System.MaintenanceMode", SystemConstants.SystemMaintenanceMode.ToString(), "حالت تعمیر و نگهداری", 0));
|
||||
@@ -141,7 +155,7 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
// Shop Settings
|
||||
response.Models.Add(CreateConfigModel("Shop.VAT", SystemConstants.ShopVAT.ToString(), "مالیات بر ارزش افزوده", 0));
|
||||
|
||||
return Task.FromResult(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -154,36 +168,43 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. تغییر از طریق کد انجام میشود"));
|
||||
}
|
||||
|
||||
#region [ARCHIVED] DeactivateConfiguration & GetConfigurationHistory — SystemConstants ثابت هستند، این RPCها بیمعنی
|
||||
[RequiresPermission(PermissionNames.SettingsManageConfiguration)]
|
||||
[Obsolete("ARCHIVED: SystemConstants are read-only — deactivation is meaningless")]
|
||||
public override Task<Empty> DeactivateConfiguration(DeactivateConfigurationRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogWarning("DeactivateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key);
|
||||
_logger.LogWarning("[ARCHIVED] DeactivateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key);
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. غیرفعالسازی از طریق کد انجام میشود"));
|
||||
}
|
||||
|
||||
[Obsolete("ARCHIVED: SystemConstants have no history — always returns empty")]
|
||||
public override Task<GetConfigurationHistoryResponse> GetConfigurationHistory(GetConfigurationHistoryRequest request, ServerCallContext context)
|
||||
{
|
||||
// چون constant هستند، تاریخچهای وجود نداره
|
||||
// [ARCHIVED] چون constant هستند، تاریخچهای وجود نداره
|
||||
return Task.FromResult(new GetConfigurationHistoryResponse());
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
private static string GetConfigurationValue(string key)
|
||||
private static string GetConfigurationValue(string key, Package? package)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
"Club.ActivationFee" => SystemConstants.ClubActivationFee.ToString(),
|
||||
"Club.MembershipGiftValue" => SystemConstants.ClubMembershipGiftValue.ToString(),
|
||||
"Club.ActivationFee" => (package?.ActivationFee ?? 0).ToString(),
|
||||
"Club.MembershipGiftValue" => (package?.ActivationFee ?? 0).ToString(),
|
||||
"Commission.MinWithdrawalAmount" => SystemConstants.CommissionMinWithdrawalAmount.ToString(),
|
||||
"Commission.MaxWeeklyBalancesPerLeg" => SystemConstants.CommissionMaxWeeklyBalancesPerLeg.ToString(),
|
||||
"Commission.MaxNetworkLevel" => SystemConstants.CommissionMaxNetworkLevel.ToString(),
|
||||
"Commission.MaxWeeklyBalancesPerLeg" => (package?.MaxBalancesPerLeg ?? 0).ToString(),
|
||||
"Commission.MaxNetworkLevel" => (package?.MaxNetworkLevel ?? 0).ToString(),
|
||||
"Commission.CashWithdrawalEnabled" => SystemConstants.CommissionCashWithdrawalEnabled.ToString(),
|
||||
"Commission.CalculationStrategy" => SystemConstants.CommissionCalculationStrategy,
|
||||
"Network.AllowOrphanNodes" => SystemConstants.NetworkAllowOrphanNodes.ToString(),
|
||||
"Network.MaxChildrenPerLeg" => SystemConstants.NetworkMaxChildrenPerLeg.ToString(),
|
||||
"Package.BasePackageAmount" => SystemConstants.BasePackageAmount.ToString(),
|
||||
"Package.DayaLoanAmount" => SystemConstants.DayaLoanAmount.ToString(),
|
||||
"Package.BasePackageAmount" => (package?.Price ?? 0).ToString(),
|
||||
"Package.DayaLoanAmount" => (package?.Price ?? 0).ToString(),
|
||||
"MagicWallet.Multiplier" => (package?.MagicWalletMultiplier ?? 0).ToString(),
|
||||
"MagicWallet.MaxDeposit" => (package?.MagicWalletMaxDeposit ?? 0).ToString(),
|
||||
"MagicWallet.MaxCredit" => (package?.MagicWalletMaxCredit ?? 0).ToString(),
|
||||
"System.MaintenanceMode" => SystemConstants.SystemMaintenanceMode.ToString(),
|
||||
"System.EnableAuditLog" => SystemConstants.SystemEnableAuditLog.ToString(),
|
||||
"Shop.VAT" => SystemConstants.ShopVAT.ToString(),
|
||||
@@ -191,21 +212,31 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<Package> GetBasePackageAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new RpcException(new Status(StatusCode.Internal, "پکیج پایه یافت نشد"));
|
||||
}
|
||||
|
||||
private static string GetConfigurationDescription(string key)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
"Club.ActivationFee" => "هزینه فعالسازی عضویت باشگاه (ریال)",
|
||||
"Club.MembershipGiftValue" => "مبلغ هدیه حق عضویت باشگاه (ریال)",
|
||||
"Commission.MinWithdrawalAmount" => "حداقل مبلغ برداشت (ریال)",
|
||||
"Club.ActivationFee" => "هزینه فعالسازی عضویت باشگاه (تومان)",
|
||||
"Club.MembershipGiftValue" => "مبلغ هدیه حق عضویت باشگاه (تومان)",
|
||||
"Commission.MinWithdrawalAmount" => "حداقل مبلغ برداشت (تومان)",
|
||||
"Commission.MaxWeeklyBalancesPerLeg" => "سقف تعادل هفتگی برای هر دست",
|
||||
"Commission.MaxNetworkLevel" => "حداکثر عمق شبکه برای محاسبه کمیسیون",
|
||||
"Commission.CashWithdrawalEnabled" => "امکان برداشت نقدی",
|
||||
"Commission.CalculationStrategy" => "روش محاسبه کمیسیون",
|
||||
"Network.AllowOrphanNodes" => "اجازه حذف والدین با فرزند",
|
||||
"Network.MaxChildrenPerLeg" => "حداکثر تعداد فرزند مستقیم در هر پا",
|
||||
"Package.BasePackageAmount" => "مبلغ پکیج پایه (ریال)",
|
||||
"Package.DayaLoanAmount" => "مبلغ وام دایا (ریال)",
|
||||
"Package.BasePackageAmount" => "مبلغ پکیج پایه (تومان)",
|
||||
"Package.DayaLoanAmount" => "مبلغ وام دایا (تومان)",
|
||||
"MagicWallet.Multiplier" => "ضریب شارژ کیفپول جادویی",
|
||||
"MagicWallet.MaxDeposit" => "سقف واریز هر دور جادویی (تومان)",
|
||||
"MagicWallet.MaxCredit" => "سقف اعتبار هر دور جادویی (تومان)",
|
||||
"System.MaintenanceMode" => "حالت تعمیر و نگهداری سیستم",
|
||||
"System.EnableAuditLog" => "فعالسازی لاگ تغییرات",
|
||||
"Shop.VAT" => "مالیات بر ارزش افزوده",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -25,9 +25,11 @@ public class HealthService : HealthContract.HealthContractBase
|
||||
return result.Adapt<GetSystemHealthResponse>();
|
||||
}
|
||||
|
||||
#region [ARCHIVED] GetServiceHealth — تکراری: GetSystemHealth کل سیستم را برمیگرداند، فیلتر client-side کافی است
|
||||
[Obsolete("ARCHIVED: Use GetSystemHealth + client-side filter by service name")]
|
||||
public override async Task<GetServiceHealthResponse> GetServiceHealth(GetServiceHealthRequest request, ServerCallContext context)
|
||||
{
|
||||
// For now, just return system health filtered by service name
|
||||
// [ARCHIVED] duplicate — GetSystemHealth returns all, client can filter
|
||||
var systemHealthQuery = new GetSystemHealthQuery();
|
||||
var systemHealth = await _mediator.Send(systemHealthQuery, context.CancellationToken);
|
||||
|
||||
@@ -45,4 +47,5 @@ public class HealthService : HealthContract.HealthContractBase
|
||||
CheckedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow)
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -3,10 +3,6 @@ using CMSMicroservice.WebApi.Common.Services;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.CreateNewPackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.UpdatePackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.DeletePackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.InitiateBasePackagePayment;
|
||||
using CMSMicroservice.Application.PackageCQ.Commands.VerifyBasePackagePayment;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetPackage;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetAllPackageByFilter;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
|
||||
@@ -72,32 +68,11 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
return await _dispatchRequestToCQRS.Handle<GetAllPackageByFilterRequest, GetAllPackageByFilterQuery, GetAllPackageByFilterResponse>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<PurchaseGoldenPackageResponse> PurchaseGoldenPackage(PurchaseGoldenPackageRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<PurchaseGoldenPackageRequest, PurchaseGoldenPackageCommand, PurchaseGoldenPackageResponse>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<VerifyGoldenPackagePurchaseResponse> VerifyGoldenPackagePurchase(VerifyGoldenPackagePurchaseRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<VerifyGoldenPackagePurchaseRequest, VerifyGoldenPackagePurchaseCommand, VerifyGoldenPackagePurchaseResponse>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<GetUserPackageStatusResponse> GetUserPackageStatus(GetUserPackageStatusRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetUserPackageStatusRequest, GetUserPackageStatusQuery, GetUserPackageStatusResponse>(request, context);
|
||||
}
|
||||
|
||||
// Base Package Payment (56M)
|
||||
public override async Task<InitiateBasePackagePaymentResponse> InitiateBasePackagePayment(InitiateBasePackagePaymentRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<InitiateBasePackagePaymentRequest, InitiateBasePackagePaymentCommand, InitiateBasePackagePaymentResponse>(request, context);
|
||||
}
|
||||
|
||||
public override async Task<VerifyBasePackagePaymentResponse> VerifyBasePackagePayment(VerifyBasePackagePaymentRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<VerifyBasePackagePaymentRequest, VerifyBasePackagePaymentCommand, VerifyBasePackagePaymentResponse>(request, context);
|
||||
}
|
||||
|
||||
// ============= Customer-specific Method Implementations =============
|
||||
|
||||
public override async Task<GetCustomerPackagesResponse> GetCustomerPackages(GetCustomerPackagesRequest request, ServerCallContext context)
|
||||
@@ -207,13 +182,17 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
.Select(u => new { u.Mobile })
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
// Callback URL از config — نه از ورودی کاربر (امنیت)
|
||||
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
||||
var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback?orderId={purchase.Id}";
|
||||
|
||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
|
||||
{
|
||||
Amount = package.Price,
|
||||
UserId = userId,
|
||||
Mobile = user?.Mobile ?? string.Empty,
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
CallbackUrl = request.CallbackUrl
|
||||
CallbackUrl = callbackUrl
|
||||
}, context.CancellationToken);
|
||||
|
||||
if (!paymentResult.IsSuccess)
|
||||
@@ -237,7 +216,7 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal",
|
||||
MerchantId = _configuration["ZarinPal:MerchantId"] ?? "",
|
||||
Amount = package.Price,
|
||||
CallbackUrl = request.CallbackUrl,
|
||||
CallbackUrl = callbackUrl,
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
Mobile = user?.Mobile,
|
||||
UserId = userId,
|
||||
@@ -295,13 +274,15 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
};
|
||||
}
|
||||
|
||||
// Verify with payment gateway
|
||||
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
||||
request.Authority, request.Status, context.CancellationToken);
|
||||
|
||||
// آپدیت PaymentTransaction
|
||||
// واکشی PaymentTransaction برای گرفتن مبلغ (تومان) جهت verify
|
||||
var paymentTx = await _context.PaymentTransactions
|
||||
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken);
|
||||
|
||||
var amountInToman = paymentTx?.Amount ?? purchase.Amount;
|
||||
|
||||
// Verify with payment gateway (مبلغ به تومان — سرویس زرینپال خودش ×۱۰ میکنه)
|
||||
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
||||
request.Authority, request.Status, (decimal)amountInToman, context.CancellationToken);
|
||||
if (paymentTx != null)
|
||||
{
|
||||
paymentTx.PaymentStatus = verifyResult.IsSuccess;
|
||||
@@ -317,6 +298,51 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Success;
|
||||
transaction.PaymentDate = DateTime.UtcNow;
|
||||
transaction.RefId = verifyResult.RefId;
|
||||
|
||||
// شارژ کیف پول کاربر
|
||||
var wallet = await _context.UserWallets
|
||||
.FirstOrDefaultAsync(w => w.UserId == purchase.UserId, context.CancellationToken);
|
||||
|
||||
if (wallet == null)
|
||||
{
|
||||
wallet = new CMSMicroservice.Domain.Entities.UserWallet
|
||||
{
|
||||
UserId = purchase.UserId,
|
||||
Balance = 0,
|
||||
DiscountBalance = 0,
|
||||
NetworkBalance = 0
|
||||
};
|
||||
_context.UserWallets.Add(wallet);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
}
|
||||
|
||||
var discountMultiplier = purchase.Package?.DiscountMultiplier
|
||||
?? throw new RpcException(new Status(StatusCode.Internal, "پکیج سفارش یافت نشد"));
|
||||
var discountAmount = (long)(purchase.Amount * (double)discountMultiplier);
|
||||
wallet.Balance += purchase.Amount;
|
||||
wallet.DiscountBalance += discountAmount;
|
||||
|
||||
// ثبت لاگ کیف پول
|
||||
var walletLog = new CMSMicroservice.Domain.Entities.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
|
||||
};
|
||||
_context.UserWalletHistories.Add(walletLog);
|
||||
|
||||
// بهروزرسانی کاربر
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == purchase.UserId, context.CancellationToken);
|
||||
if (user != null)
|
||||
user.PackagePurchaseMethod = Domain.Enums.PackagePurchaseMethod.DirectPurchase;
|
||||
}
|
||||
else if (transaction != null)
|
||||
{
|
||||
@@ -325,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,
|
||||
|
||||
@@ -162,6 +162,10 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
_context.Transactions.Add(transaction);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
// Callback URL از config — نه از ورودی کاربر (امنیت)
|
||||
var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268";
|
||||
var callbackUrl = $"{frontOfficeBaseUrl}/profile/payment-callback";
|
||||
|
||||
// Initiate payment with gateway
|
||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
|
||||
{
|
||||
@@ -169,7 +173,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
UserId = userId,
|
||||
Mobile = request.Mobile ?? user?.Mobile ?? string.Empty,
|
||||
Description = request.Description ?? "پرداخت آنلاین",
|
||||
CallbackUrl = request.CallbackUrl
|
||||
CallbackUrl = callbackUrl
|
||||
}, context.CancellationToken);
|
||||
|
||||
if (!paymentResult.IsSuccess)
|
||||
@@ -191,7 +195,7 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal",
|
||||
MerchantId = _configuration["ZarinPal:MerchantId"] ?? "",
|
||||
Amount = request.Amount,
|
||||
CallbackUrl = request.CallbackUrl,
|
||||
CallbackUrl = callbackUrl,
|
||||
Description = request.Description ?? "پرداخت آنلاین",
|
||||
Mobile = request.Mobile ?? user?.Mobile,
|
||||
UserId = userId,
|
||||
@@ -235,13 +239,15 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
};
|
||||
}
|
||||
|
||||
// Verify with gateway
|
||||
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
||||
request.Authority, request.Status, context.CancellationToken);
|
||||
|
||||
// آپدیت PaymentTransaction
|
||||
// واکشی PaymentTransaction برای گرفتن مبلغ (تومان) جهت verify
|
||||
var paymentTx = await _context.PaymentTransactions
|
||||
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, context.CancellationToken);
|
||||
|
||||
var amountInToman = paymentTx?.Amount ?? transaction.Amount;
|
||||
|
||||
// Verify with gateway (مبلغ به تومان — سرویس زرینپال خودش ×۱۰ میکنه)
|
||||
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
||||
request.Authority, request.Status, (decimal)amountInToman, context.CancellationToken);
|
||||
if (paymentTx != null)
|
||||
{
|
||||
paymentTx.PaymentStatus = verifyResult.IsSuccess;
|
||||
|
||||
@@ -322,7 +322,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
wallet.Balance -= totalAmount;
|
||||
|
||||
// Create wallet change log
|
||||
var walletLog = new UserWalletChangeLog
|
||||
var walletLog = new UserWalletHistory
|
||||
{
|
||||
WalletId = wallet.Id,
|
||||
CurrentBalance = wallet.Balance,
|
||||
@@ -335,20 +335,31 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
RefrenceId = transaction.Id
|
||||
};
|
||||
|
||||
_context.UserWalletChangeLogs.Add(walletLog);
|
||||
_context.UserWalletHistories.Add(walletLog);
|
||||
|
||||
// ═══ Magic Wallet: Entry / Exit Trigger ═══
|
||||
if (wallet.Balance == 0)
|
||||
// Q24: آستانه <= 1,000,000 ریال به جای == 0 (چون قیمت محصولات متفاوت است)
|
||||
if (wallet.Balance <= SystemConstants.MagicWalletEntryThreshold)
|
||||
{
|
||||
var user = await _context.Users
|
||||
.Include(u => u.ClubMembership)
|
||||
.FirstOrDefaultAsync(u => u.Id == userId, context.CancellationToken);
|
||||
|
||||
if (wallet.WalletMode == WalletMode.Magic
|
||||
&& wallet.MagicTotalDeposited >= SystemConstants.MagicWalletMaxDeposit)
|
||||
if (wallet.WalletMode == WalletMode.Magic)
|
||||
{
|
||||
// بارگذاری پکیج کاربر برای سقف کیفپول جادویی
|
||||
var userCycle = await _context.ClubMembershipCycles
|
||||
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken);
|
||||
var userPackage = userCycle != null
|
||||
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == userCycle.PackageId, context.CancellationToken)
|
||||
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken);
|
||||
var magicMaxDeposit = userPackage?.MagicWalletMaxDeposit
|
||||
?? throw new InvalidOperationException($"Package not found for magic wallet exit check, UserId={userId}");
|
||||
|
||||
if (wallet.MagicTotalDeposited >= magicMaxDeposit)
|
||||
{
|
||||
// ── EXIT Magic Mode ──
|
||||
// هر دو شرط: Balance=0 و سقف 100M پر شده
|
||||
// هر دو شرط: Balance<=Threshold و سقف MagicMaxDeposit پر شده
|
||||
wallet.WalletMode = WalletMode.Normal;
|
||||
wallet.MagicCompletedAt = DateTime.UtcNow;
|
||||
|
||||
@@ -358,6 +369,14 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
if (currentCycle != null)
|
||||
{
|
||||
currentCycle.MagicCompletedAt = DateTime.UtcNow;
|
||||
currentCycle.IsCurrentCycle = false;
|
||||
}
|
||||
|
||||
// B6 fix: ریست PackagePurchaseMethod تا Guards G1-G3 خرید مجدد را مسدود نکنند
|
||||
if (user != null)
|
||||
{
|
||||
user.PackagePurchaseMethod = PackagePurchaseMethod.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (wallet.WalletMode == WalletMode.Normal
|
||||
@@ -380,7 +399,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
currentCycle.MagicStartedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
// ⚠️ اگه Magic باشه و Balance=0 ولی سقف پر نشده → هنوز Magic!
|
||||
// ⚠️ اگه Magic باشه و Balance<=Threshold ولی سقف پر نشده → هنوز Magic!
|
||||
// کاربر میتونه دوباره شارژ کنه
|
||||
}
|
||||
|
||||
@@ -497,7 +516,9 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
};
|
||||
}
|
||||
|
||||
#region [ARCHIVED] GetOrdersByDateRange — تکراری: GetAllUserOrderByFilter همین قابلیت + فیلترهای بیشتر دارد
|
||||
[RequiresPermission(PermissionNames.ReportsView)]
|
||||
[Obsolete("ARCHIVED: Use GetAllUserOrderByFilter instead — has date range + more filters")]
|
||||
public override async Task<GetOrdersByDateRangeResponse> GetOrdersByDateRange(GetOrdersByDateRangeRequest request, ServerCallContext context)
|
||||
{
|
||||
var pageNumber = request.PageNumber > 0 ? request.PageNumber : 1;
|
||||
@@ -551,6 +572,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
Orders = { orders }
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
[RequiresPermission(PermissionNames.OrdersUpdate)]
|
||||
public override async Task<ApplyDiscountToOrderResponse> ApplyDiscountToOrder(ApplyDiscountToOrderRequest request, ServerCallContext context)
|
||||
@@ -582,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
|
||||
@@ -624,17 +646,21 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
|
||||
// ============= Customer-specific Methods =============
|
||||
|
||||
#region [ARCHIVED] CreateNewOrderForCustomer & SubmitOrderForCustomer — Stub خالی: مسیر سفارش مشتری از SubmitShopBuyOrder میگذرد
|
||||
[Obsolete("ARCHIVED: Empty stub — customer order flow uses SubmitShopBuyOrder")]
|
||||
public override async Task<CreateNewUserOrderResponse> CreateNewOrderForCustomer(CreateNewUserOrderRequest request, ServerCallContext context)
|
||||
{
|
||||
// For now, return empty response - will be implemented properly later
|
||||
// [ARCHIVED] Empty stub — مسیر سفارش مشتری از SubmitShopBuyOrder میگذرد
|
||||
return new CreateNewUserOrderResponse();
|
||||
}
|
||||
|
||||
[Obsolete("ARCHIVED: Empty stub — customer order flow uses SubmitShopBuyOrder")]
|
||||
public override async Task<SubmitShopBuyOrderResponse> SubmitOrderForCustomer(SubmitShopBuyOrderRequest request, ServerCallContext context)
|
||||
{
|
||||
// For now, return empty response - will be implemented properly later
|
||||
// [ARCHIVED] Empty stub — مسیر سفارش مشتری از SubmitShopBuyOrder میگذرد
|
||||
return new SubmitShopBuyOrderResponse();
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override async Task<GetAllUserOrderByFilterResponse> GetCustomerOrders(GetAllUserOrderByFilterRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -807,7 +833,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
if (wallet != null)
|
||||
{
|
||||
wallet.Balance += refundAmount;
|
||||
_context.UserWalletChangeLogs.Add(new UserWalletChangeLog
|
||||
_context.UserWalletHistories.Add(new UserWalletHistory
|
||||
{
|
||||
WalletId = wallet.Id,
|
||||
CurrentBalance = wallet.Balance,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog;
|
||||
using CMSMicroservice.WebApi.Common.Services;
|
||||
using CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.CreateNewUserWalletChangeLog;
|
||||
using CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.UpdateUserWalletChangeLog;
|
||||
using CMSMicroservice.Application.UserWalletChangeLogCQ.Commands.DeleteUserWalletChangeLog;
|
||||
using CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetUserWalletChangeLog;
|
||||
using CMSMicroservice.Application.UserWalletChangeLogCQ.Queries.GetAllUserWalletChangeLogByFilter;
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
public class UserWalletChangeLogService : UserWalletChangeLogContract.UserWalletChangeLogContractBase
|
||||
{
|
||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||
|
||||
public UserWalletChangeLogService(IDispatchRequestToCQRS dispatchRequestToCQRS)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
}
|
||||
public override async Task<CreateNewUserWalletChangeLogResponse> CreateNewUserWalletChangeLog(CreateNewUserWalletChangeLogRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<CreateNewUserWalletChangeLogRequest, CreateNewUserWalletChangeLogCommand, CreateNewUserWalletChangeLogResponse>(request, context);
|
||||
}
|
||||
public override async Task<Empty> UpdateUserWalletChangeLog(UpdateUserWalletChangeLogRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<UpdateUserWalletChangeLogRequest, UpdateUserWalletChangeLogCommand>(request, context);
|
||||
}
|
||||
public override async Task<Empty> DeleteUserWalletChangeLog(DeleteUserWalletChangeLogRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<DeleteUserWalletChangeLogRequest, DeleteUserWalletChangeLogCommand>(request, context);
|
||||
}
|
||||
public override async Task<GetUserWalletChangeLogResponse> GetUserWalletChangeLog(GetUserWalletChangeLogRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetUserWalletChangeLogRequest, GetUserWalletChangeLogQuery, GetUserWalletChangeLogResponse>(request, context);
|
||||
}
|
||||
public override async Task<GetAllUserWalletChangeLogByFilterResponse> GetAllUserWalletChangeLogByFilter(GetAllUserWalletChangeLogByFilterRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetAllUserWalletChangeLogByFilterRequest, GetAllUserWalletChangeLogByFilterQuery, GetAllUserWalletChangeLogByFilterResponse>(request, context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using CMSMicroservice.Protobuf.Protos.UserWalletHistory;
|
||||
using CMSMicroservice.WebApi.Common.Services;
|
||||
using CMSMicroservice.Application.UserWalletHistoryCQ.Commands.CreateNewUserWalletHistory;
|
||||
using CMSMicroservice.Application.UserWalletHistoryCQ.Commands.UpdateUserWalletHistory;
|
||||
using CMSMicroservice.Application.UserWalletHistoryCQ.Commands.DeleteUserWalletHistory;
|
||||
using CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetUserWalletHistory;
|
||||
using CMSMicroservice.Application.UserWalletHistoryCQ.Queries.GetAllUserWalletHistoryByFilter;
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
public class UserWalletHistoryService : UserWalletHistoryContract.UserWalletHistoryContractBase
|
||||
{
|
||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||
|
||||
public UserWalletHistoryService(IDispatchRequestToCQRS dispatchRequestToCQRS)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
}
|
||||
public override async Task<CreateNewUserWalletHistoryResponse> CreateNewUserWalletHistory(CreateNewUserWalletHistoryRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<CreateNewUserWalletHistoryRequest, CreateNewUserWalletHistoryCommand, CreateNewUserWalletHistoryResponse>(request, context);
|
||||
}
|
||||
public override async Task<Empty> UpdateUserWalletHistory(UpdateUserWalletHistoryRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<UpdateUserWalletHistoryRequest, UpdateUserWalletHistoryCommand>(request, context);
|
||||
}
|
||||
public override async Task<Empty> DeleteUserWalletHistory(DeleteUserWalletHistoryRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<DeleteUserWalletHistoryRequest, DeleteUserWalletHistoryCommand>(request, context);
|
||||
}
|
||||
public override async Task<GetUserWalletHistoryResponse> GetUserWalletHistory(GetUserWalletHistoryRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetUserWalletHistoryRequest, GetUserWalletHistoryQuery, GetUserWalletHistoryResponse>(request, context);
|
||||
}
|
||||
public override async Task<GetAllUserWalletHistoryByFilterResponse> GetAllUserWalletHistoryByFilter(GetAllUserWalletHistoryByFilterRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetAllUserWalletHistoryByFilterRequest, GetAllUserWalletHistoryByFilterQuery, GetAllUserWalletHistoryByFilterResponse>(request, context);
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,11 @@ 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.GetCustomerWalletChangeLog;
|
||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory;
|
||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals;
|
||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
@@ -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)
|
||||
{
|
||||
@@ -102,9 +108,9 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<GetCustomerWalletChangeLogResponse> GetCustomerWalletChangeLog(GetCustomerWalletChangeLogRequest request, ServerCallContext context)
|
||||
public override async Task<GetCustomerWalletHistoryResponse> GetCustomerWalletHistory(GetCustomerWalletHistoryRequest request, ServerCallContext context)
|
||||
{
|
||||
var query = new GetCustomerWalletChangeLogQuery
|
||||
var query = new GetCustomerWalletHistoryQuery
|
||||
{
|
||||
ReferenceId = request.ReferenceId,
|
||||
IsIncrease = request.IsIncrease
|
||||
@@ -112,7 +118,7 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
|
||||
|
||||
var changeLogs = await _sender.Send(query, context.CancellationToken);
|
||||
|
||||
var response = new GetCustomerWalletChangeLogResponse
|
||||
var response = new GetCustomerWalletHistoryResponse
|
||||
{
|
||||
MetaData = new CMSMicroservice.Protobuf.Protos.MetaData
|
||||
{
|
||||
@@ -127,7 +133,7 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
|
||||
|
||||
foreach (var log in changeLogs)
|
||||
{
|
||||
response.Models.Add(new CustomerWalletChangeLogModel
|
||||
response.Models.Add(new CustomerWalletHistoryModel
|
||||
{
|
||||
CurrentBalance = log.CurrentBalance,
|
||||
ChangeValue = log.ChangeValue,
|
||||
@@ -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)
|
||||
{
|
||||
@@ -227,15 +321,27 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
|
||||
var cycleCount = await _context.ClubMembershipCycles
|
||||
.CountAsync(c => c.UserId == userId, context.CancellationToken);
|
||||
|
||||
// بارگذاری پکیج کاربر برای سقف کیفپول جادویی
|
||||
var currentCycle = await _context.ClubMembershipCycles
|
||||
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken);
|
||||
var package = currentCycle != null
|
||||
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, context.CancellationToken)
|
||||
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken);
|
||||
var magicMaxDeposit = package?.MagicWalletMaxDeposit ?? SystemConstants.MagicWalletDefaultMaxDeposit;
|
||||
var magicMultiplier = package != null ? (double)package.MagicWalletMultiplier : SystemConstants.MagicWalletDefaultMultiplier;
|
||||
var magicMaxCredit = package?.MagicWalletMaxCredit ?? SystemConstants.MagicWalletDefaultMaxCredit;
|
||||
|
||||
var response = new GetMagicWalletStatusResponse
|
||||
{
|
||||
WalletMode = (int)wallet.WalletMode,
|
||||
MagicTotalDeposited = wallet.MagicTotalDeposited,
|
||||
MagicTotalCredited = wallet.MagicTotalCredited,
|
||||
MagicMaxDeposit = SystemConstants.MagicWalletMaxDeposit,
|
||||
MagicRemainingDeposit = Math.Max(0, SystemConstants.MagicWalletMaxDeposit - wallet.MagicTotalDeposited),
|
||||
MagicMaxDeposit = magicMaxDeposit,
|
||||
MagicRemainingDeposit = Math.Max(0, magicMaxDeposit - wallet.MagicTotalDeposited),
|
||||
Balance = wallet.Balance,
|
||||
PurchaseCycleCount = cycleCount
|
||||
PurchaseCycleCount = cycleCount,
|
||||
MagicMultiplier = magicMultiplier,
|
||||
MagicMaxCredit = magicMaxCredit
|
||||
};
|
||||
|
||||
if (wallet.MagicActivatedAt.HasValue)
|
||||
|
||||
Reference in New Issue
Block a user