feat: add geography entities with countries, states and cities
This commit is contained in:
+48
@@ -0,0 +1,48 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای پذیرش قرارداد باشگاه مشتریان
|
||||
/// </summary>
|
||||
public record AcceptClubMembershipContractCommand : IRequest<AcceptClubMembershipContractResponseDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه کاربر
|
||||
/// </summary>
|
||||
public long UserId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// کد OTP برای تایید
|
||||
/// </summary>
|
||||
public string OtpCode { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه یکتای امضا (GUID)
|
||||
/// </summary>
|
||||
public string SignGuid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// محتوای HTML قرارداد
|
||||
/// </summary>
|
||||
public string ContractHtml { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO پاسخ پذیرش قرارداد باشگاه مشتریان
|
||||
/// </summary>
|
||||
public class AcceptClubMembershipContractResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// آیا عملیات موفق بود؟
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// پیام نتیجه
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه قرارداد ثبت شده
|
||||
/// </summary>
|
||||
public long ContractId { get; set; }
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
|
||||
|
||||
/// <summary>
|
||||
/// Handler برای پذیرش قرارداد باشگاه مشتریان
|
||||
/// این handler:
|
||||
/// 1. کد OTP را تایید میکند
|
||||
/// 2. قرارداد را در جدول UserContract ثبت میکند
|
||||
/// 3. باشگاه مشتری را فعال میکند (IsActive = true)
|
||||
/// </summary>
|
||||
public class AcceptClubMembershipContractCommandHandler
|
||||
: IRequestHandler<AcceptClubMembershipContractCommand, AcceptClubMembershipContractResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IConfiguration _cfg;
|
||||
private readonly IHashService _hashService;
|
||||
private readonly ILogger<AcceptClubMembershipContractCommandHandler> _logger;
|
||||
|
||||
private const int MaxAttempts = 5;
|
||||
private const string OtpPurpose = "signClubContract";
|
||||
|
||||
public AcceptClubMembershipContractCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IConfiguration cfg,
|
||||
IHashService hashService,
|
||||
ILogger<AcceptClubMembershipContractCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_cfg = cfg;
|
||||
_hashService = hashService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<AcceptClubMembershipContractResponseDto> Handle(
|
||||
AcceptClubMembershipContractCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Processing club membership contract for UserId: {UserId}",
|
||||
request.UserId
|
||||
);
|
||||
|
||||
// 1. دریافت کاربر
|
||||
var user = await _context.Users
|
||||
.Include(u => u.ClubMembership)
|
||||
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("User not found: {UserId}", request.UserId);
|
||||
throw new NotFoundException(nameof(User), request.UserId);
|
||||
}
|
||||
|
||||
// 2. بررسی خرید پکیج
|
||||
if (user.PackagePurchaseMethod == PackagePurchaseMethod.None)
|
||||
{
|
||||
return new AcceptClubMembershipContractResponseDto
|
||||
{
|
||||
Success = false,
|
||||
Message = "برای امضای قرارداد باشگاه مشتریان ابتدا باید پکیج پایه را خریداری کنید"
|
||||
};
|
||||
}
|
||||
|
||||
// 3. بررسی عدم فعال بودن قبلی باشگاه
|
||||
if (user.ClubMembership?.IsActive == true)
|
||||
{
|
||||
return new AcceptClubMembershipContractResponseDto
|
||||
{
|
||||
Success = false,
|
||||
Message = "شما قبلاً عضو باشگاه مشتریان شدهاید"
|
||||
};
|
||||
}
|
||||
|
||||
// 4. تایید OTP
|
||||
var otpResult = await VerifyOtpAsync(user.Mobile, request.OtpCode, cancellationToken);
|
||||
if (!otpResult.Success)
|
||||
{
|
||||
return new AcceptClubMembershipContractResponseDto
|
||||
{
|
||||
Success = false,
|
||||
Message = otpResult.Message
|
||||
};
|
||||
}
|
||||
|
||||
// 5. ثبت قرارداد در جدول UserContract
|
||||
var contract = await _context.Contracts
|
||||
.FirstOrDefaultAsync(c => c.Type == ContractType.ClubMembership, cancellationToken);
|
||||
|
||||
if (contract == null)
|
||||
{
|
||||
// اگر قرارداد وجود ندارد، یک قرارداد پیشفرض ایجاد کنید
|
||||
contract = new Contract
|
||||
{
|
||||
Title = "قرارداد باشگاه مشتریان",
|
||||
Description = "قوانین و مقررات باشگاه مشتریان کارابازار",
|
||||
HtmlContent = request.ContractHtml,
|
||||
Type = ContractType.ClubMembership
|
||||
};
|
||||
await _context.Contracts.AddAsync(contract, cancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var userContract = new UserContract
|
||||
{
|
||||
UserId = user.Id,
|
||||
ContractId = contract.Id,
|
||||
SignGuid = request.SignGuid,
|
||||
SignedPdfFile = request.ContractHtml
|
||||
};
|
||||
await _context.UserContracts.AddAsync(userContract, cancellationToken);
|
||||
|
||||
// 6. فعالسازی باشگاه مشتریان
|
||||
if (user.ClubMembership == null)
|
||||
{
|
||||
user.ClubMembership = new Domain.Entities.Club.ClubMembership
|
||||
{
|
||||
UserId = user.Id,
|
||||
IsActive = true,
|
||||
ActivatedAt = DateTime.Now,
|
||||
InitialContribution = 56_000_000,
|
||||
GiftValue = 25_200_000,
|
||||
PurchaseMethod = user.PackagePurchaseMethod
|
||||
};
|
||||
await _context.ClubMemberships.AddAsync(user.ClubMembership, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
user.ClubMembership.IsActive = true;
|
||||
user.ClubMembership.ActivatedAt = DateTime.Now;
|
||||
_context.ClubMemberships.Update(user.ClubMembership);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Club membership contract accepted and activated for UserId: {UserId}, ContractId: {ContractId}",
|
||||
request.UserId,
|
||||
userContract.Id
|
||||
);
|
||||
|
||||
return new AcceptClubMembershipContractResponseDto
|
||||
{
|
||||
Success = true,
|
||||
Message = "قرارداد باشگاه مشتریان با موفقیت ثبت شد و عضویت شما فعال گردید",
|
||||
ContractId = userContract.Id
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<(bool Success, string Message)> VerifyOtpAsync(
|
||||
string mobile,
|
||||
string code,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var normalizedMobile = mobile.NormalizeIranMobile();
|
||||
var now = DateTime.Now;
|
||||
|
||||
var otp = await _context.OtpTokens
|
||||
.Where(o => o.Mobile == normalizedMobile
|
||||
&& o.Purpose == OtpPurpose
|
||||
&& !o.IsUsed
|
||||
&& o.ExpiresAt > now)
|
||||
.OrderByDescending(o => o.Created)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (otp == null)
|
||||
{
|
||||
return (false, "کد تایید پیدا نشد یا منقضی شده است");
|
||||
}
|
||||
|
||||
if (otp.Attempts >= MaxAttempts)
|
||||
{
|
||||
return (false, "تعداد تلاشها بیش از حد مجاز است. لطفاً کد جدید دریافت کنید");
|
||||
}
|
||||
|
||||
otp.Attempts++;
|
||||
|
||||
var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not configured");
|
||||
|
||||
if (!_hashService.VerifyHmacSha256Hex(code, otp.CodeHash, secret))
|
||||
{
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
return (false, "کد تایید نادرست است");
|
||||
}
|
||||
|
||||
// کد صحیح است - علامتگذاری به عنوان استفاده شده
|
||||
otp.IsUsed = true;
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return (true, "کد تایید صحیح است");
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
|
||||
|
||||
public class AcceptClubMembershipContractCommandValidator : AbstractValidator<AcceptClubMembershipContractCommand>
|
||||
{
|
||||
public AcceptClubMembershipContractCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر الزامی است");
|
||||
|
||||
RuleFor(x => x.OtpCode)
|
||||
.NotEmpty()
|
||||
.WithMessage("کد تایید الزامی است")
|
||||
.Length(6)
|
||||
.WithMessage("کد تایید باید ۶ رقم باشد");
|
||||
|
||||
RuleFor(x => x.SignGuid)
|
||||
.NotEmpty()
|
||||
.WithMessage("شناسه امضا الزامی است");
|
||||
|
||||
RuleFor(x => x.ContractHtml)
|
||||
.NotEmpty()
|
||||
.WithMessage("محتوای قرارداد الزامی است");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user