feat: add club membership contract signing and city services

This commit is contained in:
masoodafar-web
2025-12-18 00:44:44 +03:30
parent 63f05e0883
commit 4330ec3726
38 changed files with 1583 additions and 25 deletions
@@ -0,0 +1,33 @@
namespace FrontOffice.BFF.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
/// <summary>
/// Command برای امضای قرارداد باشگاه مشتریان
/// </summary>
public record AcceptClubMembershipContractCommand : IRequest<AcceptClubMembershipContractResponseDto>
{
/// <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
{
public bool Success { get; set; }
public string Message { get; set; }
public long ContractId { get; set; }
public string Token { get; set; }
}
@@ -0,0 +1,80 @@
using CMSMicroservice.Protobuf.Protos.ClubMembership;
namespace FrontOffice.BFF.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
/// <summary>
/// Handler برای امضای قرارداد باشگاه مشتریان
/// 1. فراخوانی CMS برای ثبت قرارداد و فعالسازی باشگاه
/// 2. دریافت توکن جدید با claims به‌روز شده (IsClubMemberActive = true)
/// </summary>
public class AcceptClubMembershipContractCommandHandler
: IRequestHandler<AcceptClubMembershipContractCommand, AcceptClubMembershipContractResponseDto>
{
private readonly IApplicationContractContext _context;
private readonly ICurrentUserService _currentUserService;
public AcceptClubMembershipContractCommandHandler(
IApplicationContractContext context,
ICurrentUserService currentUserService)
{
_context = context;
_currentUserService = currentUserService;
}
public async Task<AcceptClubMembershipContractResponseDto> Handle(
AcceptClubMembershipContractCommand request,
CancellationToken cancellationToken)
{
var userId = _currentUserService.UserId
?? throw new ForbiddenAccessException();
// 1. فراخوانی CMS برای ثبت قرارداد و فعالسازی باشگاه
var cmsResponse = await _context.ClubMemberships.AcceptClubMembershipContractAsync(
new AcceptClubMembershipContractRequest
{
UserId = userId,
OtpCode = request.OtpCode,
SignGuid = request.SignGuid,
ContractHtml = request.ContractHtml
},
cancellationToken: cancellationToken);
if (!cmsResponse.Success)
{
return new AcceptClubMembershipContractResponseDto
{
Success = false,
Message = cmsResponse.Message,
ContractId = 0,
Token = null
};
}
// 2. دریافت توکن جدید با claims به‌روز شده
string newToken = null;
try
{
var tokenResponse = await _context.User.GetJwtTokenAsync(
new CMSMicroservice.Protobuf.Protos.User.GetJwtTokenRequest
{
Id = userId
},
cancellationToken: cancellationToken);
newToken = tokenResponse?.Token;
}
catch
{
// اگر دریافت توکن با خطا مواجه شد، عملیات اصلی موفق بوده
// کاربر می‌تواند با login مجدد توکن جدید بگیرد
}
return new AcceptClubMembershipContractResponseDto
{
Success = true,
Message = cmsResponse.Message,
ContractId = cmsResponse.ContractId,
Token = newToken
};
}
}
@@ -0,0 +1,21 @@
namespace FrontOffice.BFF.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;
public class AcceptClubMembershipContractCommandValidator : AbstractValidator<AcceptClubMembershipContractCommand>
{
public AcceptClubMembershipContractCommandValidator()
{
RuleFor(x => x.OtpCode)
.NotEmpty()
.WithMessage("کد تایید الزامی است")
.Length(6)
.WithMessage("کد تایید باید ۶ رقم باشد");
RuleFor(x => x.SignGuid)
.NotEmpty()
.WithMessage("شناسه امضا الزامی است");
RuleFor(x => x.ContractHtml)
.NotEmpty()
.WithMessage("محتوای قرارداد الزامی است");
}
}
@@ -0,0 +1,23 @@
namespace FrontOffice.BFF.Application.ClubMembershipCQ.Commands.RequestClubContractOtp;
/// <summary>
/// Command برای درخواست OTP امضای قرارداد باشگاه مشتریان
/// </summary>
public record RequestClubContractOtpCommand : IRequest<RequestClubContractOtpResponseDto>
{
/// <summary>
/// شناسه یکتای امضا (GUID)
/// </summary>
public string SignGuid { get; init; }
}
/// <summary>
/// DTO پاسخ درخواست OTP
/// </summary>
public class RequestClubContractOtpResponseDto
{
public bool Success { get; set; }
public string Message { get; set; }
public int RemainingAttempts { get; set; }
public int RemainingSeconds { get; set; }
}
@@ -0,0 +1,80 @@
using System.Text;
using CMSMicroservice.Protobuf.Protos.OtpToken;
namespace FrontOffice.BFF.Application.ClubMembershipCQ.Commands.RequestClubContractOtp;
/// <summary>
/// Handler برای درخواست OTP امضای قرارداد باشگاه مشتریان
/// از سرویس OTP موجود در CMS استفاده می‌کند و پیامک ارسال می‌کند
/// </summary>
public class RequestClubContractOtpCommandHandler
: IRequestHandler<RequestClubContractOtpCommand, RequestClubContractOtpResponseDto>
{
private readonly IApplicationContractContext _context;
private readonly IKavenegarService _kavenegarService;
private readonly ICurrentUserService _currentUserService;
private const string OtpPurpose = "signClubContract";
public RequestClubContractOtpCommandHandler(
IApplicationContractContext context,
IKavenegarService kavenegarService,
ICurrentUserService currentUserService)
{
_context = context;
_kavenegarService = kavenegarService;
_currentUserService = currentUserService;
}
public async Task<RequestClubContractOtpResponseDto> Handle(
RequestClubContractOtpCommand request,
CancellationToken cancellationToken)
{
// دریافت شماره موبایل از توکن کاربر
var mobileNumber = _currentUserService.MobileNumber;
if (string.IsNullOrEmpty(mobileNumber))
{
return new RequestClubContractOtpResponseDto
{
Success = false,
Message = "شماره موبایل کاربر یافت نشد"
};
}
// فراخوانی سرویس OTP در CMS
var otpResponse = await _context.OtpToken.CreateNewOtpTokenAsync(
new CreateNewOtpTokenRequest
{
Mobile = mobileNumber,
Purpose = OtpPurpose
},
cancellationToken: cancellationToken);
// ارسال پیامک با کد OTP
if (otpResponse.Success && !string.IsNullOrWhiteSpace(otpResponse.Code))
{
var fullName = $"{_currentUserService.FirstName} {_currentUserService.LastName}".Trim();
await _kavenegarService.Send(
mobile: mobileNumber,
new StringBuilder("سلام ")
.Append(string.IsNullOrEmpty(fullName) ? "کاربر" : fullName)
.AppendLine(" عزیز")
.Append("کد یک بار مصرف برای تایید قرارداد باشگاه مشتریان: ")
.AppendLine(otpResponse.Code)
.AppendLine("شناسه امضاء: ")
.AppendLine(request.SignGuid)
.AppendLine("کارابازار")
.ToString());
}
return new RequestClubContractOtpResponseDto
{
Success = otpResponse.Success,
Message = otpResponse.Message,
RemainingAttempts = otpResponse.RemainingAttempts,
RemainingSeconds = otpResponse.RemainingSeconds
};
}
}
@@ -0,0 +1,11 @@
namespace FrontOffice.BFF.Application.ClubMembershipCQ.Commands.RequestClubContractOtp;
public class RequestClubContractOtpCommandValidator : AbstractValidator<RequestClubContractOtpCommand>
{
public RequestClubContractOtpCommandValidator()
{
RuleFor(x => x.SignGuid)
.NotEmpty()
.WithMessage("شناسه امضا الزامی است");
}
}