using Common; using Common.Constant; using Common.Http; using Common.Logic; //using Esim.Apis.Logic; using Esim.Apis.Singleton; using Database; using Database.Database; using log4net; using Microsoft.AspNetCore.Mvc; using Microsoft.VisualBasic; using Newtonsoft.Json; using System; using System.Threading.Tasks; using System.Xml.Serialization; namespace Esim.Apis.Business { public class UserBusinessImpl : IUserBusiness { private static readonly log4net.ILog log = log4net.LogManager.GetLogger( typeof(UserBusinessImpl) ); private ModelContext dbContext; IConfiguration configuration; public UserBusinessImpl(ModelContext _dbContext, IConfiguration _configuration) { dbContext = _dbContext; configuration = _configuration; } private string GetParameter(string key) { return configuration.GetSection(key).Value ?? ""; } //public async Task DetectMsisdn( // HttpRequest httpRequest, // DetectMsisdnReq request //) //{ // var url = httpRequest.Path; // var json = JsonConvert.SerializeObject(request); // log.Debug("URL: " + url + " => Request: " + json); // try // { // //// add prize to user // //var addPrizeResult = await QuestLogic.AddPrizeForUser( // // dbContext, // // "67075723423", // // 101 // //); // var msisdn = DotnetLib.Logic.ReuseLogic.TelemorValidateMsisdn(GetParameter("CountryCode"), request.msisdn); // if (msisdn == null) // { // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // ConfigManager.Instance.GetConfigWebValue("MSISDN_INVALID"), // new { } // ); // } // Account? account = null; // if (request.accessToken != null) // { // // check access token // account = dbContext // .Accounts.Where(x => x.Msisdn == msisdn) // .FirstOrDefault(); // if (account == null) // { // // create account // Account accountCreate = new Account // { // Id = (decimal)await DbLogic.GenIdAsync(dbContext, "ACCOUNT_SEQ"), // Msisdn = msisdn, // Password = CommonLogic.GenPassword(6), // RefreshToken = request.accessToken, // Username = msisdn, // Birthday = DateTime.Now, // CreatedDate = DateTime.Now, // }; // dbContext.Accounts.Add(accountCreate); // await dbContext.SaveChangesAsync(); // account = accountCreate; // } // } // else if (request.msisdn != null && request.password != null) // { // // after checking access token // // check user // account = dbContext // .Accounts.Where(x => x.Msisdn == msisdn && x.Password == request.password) // .FirstOrDefault(); // if (account == null) // { // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // "Invalid msisdn or password", // new { } // ); // } // } // else // { // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // "Invalid msisdn or password", // new { } // ); // } // // gen token // string token = CommonLogic.GenToken(configuration, account.Msisdn, account.Id.ToString()); // string refreshToken = CommonLogic.GenRefreshToken(configuration, account.Msisdn); // // update token // account.RefreshToken = token; // dbContext.Accounts.Update(account); // await dbContext.SaveChangesAsync(); // // get inviting mission // var mission = dbContext // .Missions.Where(x => // x.Type == CommonConstant.TypeInviting // && x.Status == CommonConstant.StatusActive // && x.FromTime <= DateTime.Now // && x.ToTime >= DateTime.Now // ).FirstOrDefault(); // if (mission != null) // { // // check user inviting // var inviting = dbContext.InvitingHistories.Where( // x => x.Receiver == account.Msisdn && x.Status == CommonConstant.StatusSuccess && x.InvitedTime >= mission.FromTime && x.InvitedTime <= mission.ToTime // ).FirstOrDefault(); // if (inviting != null) // { // inviting.Status = CommonConstant.StatusClaimed; // dbContext.InvitingHistories.Update(inviting); // await dbContext.SaveChangesAsync(); // // check time required // var invitingClaimed = dbContext.InvitingHistories.Where(x => x.Msisdn == inviting.Msisdn && x.Status == CommonConstant.StatusClaimed && x.InvitedTime >= mission.FromTime && x.InvitedTime <= mission.ToTime).Count(); // if (invitingClaimed >= mission.RequiredTime) // { // log.Debug("User invited, add prize for user"); // var accountInviting = dbContext.Accounts.Where(x => x.Msisdn == inviting.Msisdn).FirstOrDefault(); // if (accountInviting == null) // { // log.Error("Not found account for inviting msisdn: " + inviting.Msisdn); // } // else // { // // update mission completed status to claimed // await QuestLogic.SetCompletedMissionForUserAsync(dbContext, accountInviting.Msisdn, accountInviting.Id, mission); // // add prize for user // var addPrizeRes = await QuestLogic.AddPrizeForUser( // dbContext, // accountInviting.Msisdn, // mission.Id // ); // // send mt to owner // await MtLogic.SendMt( // dbContext, // accountInviting.Msisdn, // ConfigManager.Instance.GetConfigSmsValue("OWNER_INVITE_SUCCESS") // ); // } // } // } // else // { // log.Debug($"No inviting mission implement for {account.Msisdn}"); // } // } // bool canPlay = true; // decimal? vendorPackageId = null; // // check if user completed vendor package // var campaignOpening = dbContext // .Campaigns.Where( // x => // x.Status == CommonConstant.StatusActive // && x.FromTime <= DateTime.Now // && x.ToTime >= DateTime.Now // ) // .FirstOrDefault(); // if (campaignOpening != null) // { // // check login history // var now = DateTime.Now; // // get config // var timeDiff = ConfigManager.Instance.GetConfigAppValue("LOGIN_TIME_DIFF") ?? "1440"; // var missionLogin = dbContext // .Missions.Where(x => x.Type == CommonConstant.TypeLogin && x.CampaignId == campaignOpening.Id) // .FirstOrDefault(); // if (missionLogin != null) // { // // kiểm tra mission đã được set completed chưa // var checkCompleted = dbContext // .MissionCompleteds.Where( // x => // x.Msisdn == account.Msisdn // && x.AccountId == account.Id // && x.MissionId == missionLogin.Id // ) // .Count(); // if (checkCompleted > 0) // { // // mission completed rồi, không cần check login nữa // log.Debug($"Misson login {missionLogin.Id} has completed for {msisdn}"); // } // else // { // log.Debug($"Checking login mission {missionLogin.Id} for {msisdn}"); // // check time required // var start = DateTime.Now.AddMinutes(-(int)missionLogin.RequiredTime * int.Parse(timeDiff)); // var completedLogin = dbContext // .MissionHistories.Where( // x => // x.Msisdn == account.Msisdn // && x.AccountId == account.Id // && x.MissionId == missionLogin.Id // && x.ExecutedTime >= start // && x.ExecutedTime <= DateTime.Now // ) // .Count(); // // lần login này đc tính vào completed login // if (completedLogin >= missionLogin.RequiredTime - 1) // { // // save history for this login // var missionHistory = new MissionHistory // { // Id = (decimal)await DbLogic.GenIdAsync(dbContext, "MISSION_HISTORY_SEQ"), // Msisdn = account.Msisdn, // MissionId = missionLogin.Id, // ExecutedTime = DateTime.Now, // Status = CommonConstant.StatusSuccess, // AccountId = account.Id // }; // dbContext.MissionHistories.Add(missionHistory); // await dbContext.SaveChangesAsync(); // // set completed mission // var completedMission = await QuestLogic.SetCompletedMissionForUserAsync( // dbContext, // account.Msisdn, // account.Id, // missionLogin // ); // if (completedMission != CommonErrorCode.Success) // { // log.Error("Set completed mission error: " + completedMission); // } // else // { // log.Debug("Set completed mission successfully"); // } // } // else // { // // check time diff to save history // var startDay = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0); // var endDay = startDay.AddMinutes(int.Parse(timeDiff)); // var loginHistory = dbContext // .MissionHistories.Where( // x => // x.Msisdn == account.Msisdn // && x.AccountId == account.Id // && x.ExecutedTime >= startDay // && x.ExecutedTime <= endDay // && x.MissionId == missionLogin.Id // ) // .FirstOrDefault(); // if (loginHistory == null) // { // // add history // var missionHistory = new MissionHistory // { // Id = (decimal)await DbLogic.GenIdAsync(dbContext, "MISSION_HISTORY_SEQ"), // Msisdn = account.Msisdn, // MissionId = missionLogin.Id, // ExecutedTime = DateTime.Now, // Status = CommonConstant.StatusSuccess, // AccountId = account.Id // }; // dbContext.MissionHistories.Add(missionHistory); // await dbContext.SaveChangesAsync(); // // add prize for user // if (missionLogin.PrizeIdEachTime != null && missionLogin.PrizeIdEachTime > 0) // { // var addPrizeRes = await QuestLogic.AddPrizeForUser( // dbContext, // account.Msisdn, // missionLogin.Id, // missionLogin.PrizeIdEachTime // ); // } // } // else // { // log.Debug($"User already login in {timeDiff}"); // } // } // } // } // // check vendor package // var vendorPackage = dbContext // .VendorPackages.Where( // x => // (x.Id == campaignOpening.VendorPackageId) // && x.Status == CommonConstant.StatusActive // ) // .FirstOrDefault(); // if (vendorPackage == null) // { // log.Error("Not found vendor package"); // canPlay = true; // } // else // { // vendorPackageId = vendorPackage.Id; // // check history // var vendorPackageHistory = dbContext // .VendorPackageHistories.Where( // x => // x.Msisdn == account.Msisdn // && x.VendorPackageId == vendorPackage.Id // && x.Status == CommonConstant.StatusSuccess // // && x.CompletedTime >= startDay // // && x.CompletedTime <= endDay // ) // .OrderByDescending(x => x.CompletedTime) // .FirstOrDefault(); // bool canCheckRegister = false; // if (vendorPackageHistory != null) // { // // check if completed today // var endTime = vendorPackage.Period == "DAILY" // ? vendorPackageHistory.CompletedTime.AddDays(1) // : vendorPackage.Period == "WEEKLY" // ? vendorPackageHistory.CompletedTime.AddDays(7) // : vendorPackage.Period == "MONTHLY" // ? vendorPackageHistory.CompletedTime.AddMonths(1) // : vendorPackage.Period == "YEARLY" // ? vendorPackageHistory.CompletedTime.AddYears(1) // : vendorPackageHistory.CompletedTime; // if (DateTime.Now <= endTime) // { // log.Debug($"User {account.Msisdn} has completed vendor package {vendorPackage.Code} today"); // canPlay = true; // } // else // { // canCheckRegister = true; // } // } // else // { // canCheckRegister = true; // } // if (canCheckRegister) // { // // check user registered service // int checkRegistered = await QuestLogic.CheckRegisterServiceForUserAsync(dbContext, account.Msisdn, vendorPackage.Code); // if (checkRegistered == CommonConstant.UserRegistered) // { // log.Debug($"User {account.Msisdn} registered vendor package {vendorPackage.Code}"); // canPlay = true; // // save vendor package history // var vendorPackageHistoryNew = new VendorPackageHistory // { // Id = (decimal)await DbLogic.GenIdAsync(dbContext, "VENDOR_PACKAGE_HISTORY_SEQ"), // Msisdn = account.Msisdn, // VendorPackageId = vendorPackage.Id, // Status = CommonConstant.StatusSuccess, // CompletedTime = DateTime.Now, // AccountId = account.Id // }; // dbContext.VendorPackageHistories.Add(vendorPackageHistoryNew); // await dbContext.SaveChangesAsync(); // } // else // { // log.Debug($"User {account.Msisdn} not registered vendor package {vendorPackage.Code}"); // canPlay = false; // } // } // } // } // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Success, // canPlay ? ConfigManager.Instance.GetConfigWebValue("NEED_BUY_VENDOR") : "Success", // new // { // token, // refreshToken, // account = new // { // account.Msisdn, // account.Username, // account.Birthday // }, // canPlay, // vendorPackageId // } // ); // } // catch (Exception exception) // { // log.Error("Exception: ", exception); // } // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"), // new { } // ); //} //public async Task UserInvite(HttpRequest httpRequest, UserInviteReq request) //{ // var url = httpRequest.Path; // var json = JsonConvert.SerializeObject(request); // log.Debug("URL: " + url + " => Request: " + json); // try // { // var msisdn = CommonLogic.GetDataFromToken(configuration, httpRequest, "Msisdn"); // var msisdnInvited = DotnetLib.Logic.ReuseLogic.TelemorValidateMsisdn(GetParameter("CountryCode"), request.receiver); // if (msisdnInvited == null) // { // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // "Invalid msisdn", // new { } // ); // } // log.Debug("msisdn: " + msisdn); // log.Debug("msisdnInvited: " + msisdnInvited); // // check time inviting // var inviting = dbContext // .InvitingHistories.Where(x => x.Receiver == msisdnInvited) // .OrderByDescending(x => x.InvitedTime) // .ToList(); // if (inviting.Count > 0) // { // if (inviting.Any(x => x.Status == CommonConstant.StatusClaimed)) // { // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // ConfigManager.Instance.GetConfigWebValue("INVITE_BEFORE"), // new { } // ); // } // else if (inviting.Any(x => x.InvitedTime.AddHours(24) >= DateTime.Now)) // { // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // ConfigManager.Instance.GetConfigWebValue("INVITE_BEFORE"), // new { } // ); // } // } // // this user is valid for inviting // var getInvitingHis = inviting.FindAll( // x => x.Msisdn == msisdn && x.Receiver == msisdnInvited // ); // if (getInvitingHis.Count() > 0) // { // getInvitingHis[0].InvitedTime = DateTime.Now; // getInvitingHis[0].Status = CommonConstant.StatusSuccess; // dbContext.InvitingHistories.Update(getInvitingHis[0]); // } // else // { // var invitingCreate = new InvitingHistory // { // Id = (decimal)await DbLogic.GenIdAsync(dbContext, "INVITING_HISTORY_SEQ"), // Msisdn = msisdn!, // Receiver = msisdnInvited, // InvitedTime = DateTime.Now, // Status = CommonConstant.StatusSuccess, // AccountId = decimal.Parse(CommonLogic.GetDataFromToken(configuration, httpRequest, "AccountId")!) // }; // dbContext.InvitingHistories.Add(invitingCreate); // // send mt to owner // await MtLogic.SendMt( // dbContext, // msisdnInvited, // ConfigManager.Instance.GetConfigSmsValue("PARTNER_INVITE_SUCCESS") // ); // } // await dbContext.SaveChangesAsync(); // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Success, // ConfigManager.Instance.GetConfigWebValue("INVITE_SUCCESS"), // new { } // ); // } // catch (Exception exception) // { // log.Error("Exception: ", exception); // } // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"), // new { } // ); //} //public async Task VendorPackageRegister( // HttpRequest httpRequest, // VendorPackageRegisterReq request //) //{ // var url = httpRequest.Path; // var json = JsonConvert.SerializeObject(request); // log.Debug("URL: " + url + " => Request: " + json); // try // { // var msisdn = CommonLogic.GetDataFromToken(configuration, httpRequest, "Msisdn"); // if (msisdn == null) // { // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // ConfigManager.Instance.GetConfigWebValue("MSISDN_INVALID"), // new { } // ); // } // log.Debug("msisdn: " + msisdn); // // call soap to register // var webService = dbContext // .Webservices.Where(x => x.WsCode == "SUBSCRIBE") // .FirstOrDefault(); // if (webService == null) // { // log.Error("Not found webservice SUBSCRIBE"); // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // "Not found webservice SUBSCRIBE", // new { } // ); // } // // check vendor package // var vendorPackage = dbContext // .VendorPackages.Where( // x => // x.Id == request.vendorPackageId // && x.Status == CommonConstant.StatusActive // ) // .FirstOrDefault(); // if (vendorPackage == null) // { // log.Error("Not found vendor package"); // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // "Not found vendor package", // new { } // ); // } // // check payment channel // var paymentChannel = dbContext // .PaymentChannels.Where( // x => // x.Id == request.paymentChannelId // && x.Status == CommonConstant.StatusActive // ) // .FirstOrDefault(); // if (paymentChannel == null) // { // log.Error("Not found payment channel"); // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // "Not found payment channel", // new { } // ); // } // string transactionId = DotnetLib.Logic.ReuseLogic.GenerateUniqueNumber(); // string body = webService // .MsgTemplate!.Replace("#MSISDN#", msisdn) // .Replace("#SERVICE_ID#", vendorPackage.Code) // .Replace("#PARAM#", "0") // .Replace("#TRANS_ID#", transactionId); // var (errorCode, envelope) = await QuestLogic.RegisterServiceForUserAsync( // dbContext, // webService.Wsdl!, // msisdn, // body, // vendorPackage.Code, // transactionId // ); // if (errorCode != CommonErrorCode.Success || envelope == null) // { // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // ConfigManager.Instance.GetConfigWebValue("REGISTER_FAILED"), // new { } // ); // } // // Lấy danh sách gói: // var subscriberResponse = envelope.Body!.SubscriberResponse; // if (subscriberResponse != null && subscriberResponse.ResponseCode == "0") // { // log.Debug("Register vendor package successfully"); // // save vendor package register to db // var vendorPackageHistory = new VendorPackageHistory // { // Id = (decimal)await DbLogic.GenIdAsync(dbContext, "VENDOR_PACKAGE_HISTORY_SEQ"), // Msisdn = msisdn!, // VendorPackageId = vendorPackage.Id, // Status = CommonConstant.StatusSuccess, // CompletedTime = DateTime.Now, // AccountId = decimal.Parse(CommonLogic.GetDataFromToken(configuration, httpRequest, "AccountId")!) // }; // // send mt // await MtLogic.SendMt( // dbContext, // msisdn!, // ConfigManager.Instance.GetConfigSmsValue("VENDOR_BUYING_SUCCESS").Replace("%MONEY%", vendorPackage.Price.ToString()) // ); // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Success, // ConfigManager.Instance.GetConfigWebValue("REGISTER_SUCCESS").Replace("%SERVICE%", vendorPackage.Code), // new { } // ); // } // else // { // log.Error("Register vendor package failed"); // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // ConfigManager.Instance.GetConfigWebValue("REGISTER_FAILED"), // new { } // ); // } // } // catch (Exception exception) // { // log.Error("Exception: ", exception); // } // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"), // new { } // ); //} //public async Task LoadVendorPackage(HttpRequest httpRequest, LoadVendorPackageReq request) //{ // var url = httpRequest.Path; // var json = JsonConvert.SerializeObject(request); // log.Debug("URL: " + url + " => Request: " + json); // try // { // // check if user completed vendor package // var campaignOpening = dbContext // .Campaigns.Where( // x => // x.Status == CommonConstant.StatusActive // && x.FromTime <= DateTime.Now // && x.ToTime >= DateTime.Now // ) // .FirstOrDefault(); // if (campaignOpening == null) // { // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // "No active campaign", // new { } // ); // } // // load vendor package // var vendorPackage = dbContext // .VendorPackages // .Where(x => x.Status == CommonConstant.StatusActive && x.Id == campaignOpening.VendorPackageId) // .Select(x => new // { // x.Id, // x.Name, // x.Code, // x.Description, // x.Price, // x.Type, // x.Period, // x.MoneyType, // x.Introduction, // }) // .FirstOrDefault(); // if (vendorPackage == null) // { // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // "No vendor package found", // new { } // ); // } // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Success, // "Load vendor package successfully", // new // { // vendorPackage // } // ); // } // catch (Exception exception) // { // log.Error("Exception: ", exception); // } // return DotnetLib.Http.HttpResponse.BuildResponse( // log, // url, // json, // CommonErrorCode.Error, // ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"), // new { } // ); //} #region Auth Methods - OTP Login /// /// Request OTP to be sent to email /// public async Task RequestOtp(HttpRequest httpRequest, RequestOtpReq request) { var url = httpRequest.Path; var json = JsonConvert.SerializeObject(request); log.Debug("URL: " + url + " => Request: " + json); try { if (string.IsNullOrEmpty(request.email)) { return DotnetLib.Http.HttpResponse.BuildResponse( log, url, json, CommonErrorCode.RequiredFieldMissing, "Email is required", new { } ); } // Generate 6-digit OTP string otpCode = GenerateOtp(); // Check if customer exists, if not create new var customer = dbContext.CustomerInfos .Where(c => c.Email == request.email) .FirstOrDefault(); int? customerId = customer?.Id; if (customer == null) { // Create new customer record var newCustomerId = (int)await Database.DbLogic.GenIdAsync(dbContext, "CUSTOMER_INFO_SEQ"); var newCustomer = new CustomerInfo { Id = newCustomerId, Email = request.email, Status = true, IsVerified = false, CreatedDate = DateTime.Now, LastUpdate = DateTime.Now }; dbContext.CustomerInfos.Add(newCustomer); await dbContext.SaveChangesAsync(); customerId = newCustomerId; } // Invalidate previous unused OTPs for this email var oldOtps = dbContext.OtpVerifications .Where(o => o.UserEmail == request.email && o.IsUsed == false) .ToList(); foreach (var oldOtp in oldOtps) { oldOtp.IsUsed = true; } // Create new OTP record int otpExpireMinutes = 5; var otpId = (int)await Database.DbLogic.GenIdAsync(dbContext, "OTP_VERIFICATION_SEQ"); var otpVerification = new OtpVerification { Id = otpId, CustomerId = customerId, UserEmail = request.email, OtpCode = otpCode, OtpType = 1, // Login OTP ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes), IsUsed = false, AttemptCount = 0, CreatedDate = DateTime.Now }; dbContext.OtpVerifications.Add(otpVerification); await dbContext.SaveChangesAsync(); // Add to MESSAGE_QUEUE for background email sending using template var messageId = (int)await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ"); // Determine template code based on language // TemplateCode format: OTP_LOGIN (default Vietnamese), OTP_LOGIN_EN, OTP_LOGIN_LO string lang = (request.lang ?? "vi").ToLower(); string templateCode = lang switch { "en" => "OTP_LOGIN_EN", "lo" => "OTP_LOGIN_LO", _ => "OTP_LOGIN" // Default Vietnamese }; // Prepare template data as JSON var templateData = System.Text.Json.JsonSerializer.Serialize(new { OTP_CODE = otpCode, EXPIRE_MINUTES = otpExpireMinutes.ToString() }); var emailMessage = new MessageQueue { Id = messageId, MessageType = 1, // Email Recipient = request.email, TemplateCode = templateCode, // Use template based on language TemplateData = templateData, Priority = true, // High priority Status = 0, // Pending ScheduledAt = DateTime.Now, RetryCount = 0, MaxRetry = 3, CreatedBy = customerId, CreatedDate = DateTime.Now }; dbContext.MessageQueues.Add(emailMessage); await dbContext.SaveChangesAsync(); log.Info($"OTP generated for {request.email}: {otpCode} - Email queued"); return DotnetLib.Http.HttpResponse.BuildResponse( log, url, json, CommonErrorCode.Success, "OTP sent successfully", new { email = request.email, expireInSeconds = otpExpireMinutes * 60 } ); } catch (Exception exception) { log.Error("Exception: ", exception); } return DotnetLib.Http.HttpResponse.BuildResponse( log, url, json, CommonErrorCode.SystemError, ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"), new { } ); } /// /// Verify OTP and complete login - return JWT token /// public async Task VerifyOtp(HttpRequest httpRequest, VerifyOtpReq request) { var url = httpRequest.Path; var json = JsonConvert.SerializeObject(request); log.Debug("URL: " + url + " => Request: " + json); try { if (string.IsNullOrEmpty(request.email) || string.IsNullOrEmpty(request.otpCode)) { return DotnetLib.Http.HttpResponse.BuildResponse( log, url, json, CommonErrorCode.RequiredFieldMissing, "Email and OTP are required", new { } ); } // Find valid OTP var otpRecord = dbContext.OtpVerifications .Where(o => o.UserEmail == request.email && o.OtpCode == request.otpCode && o.IsUsed == false && o.ExpiredAt > DateTime.Now) .OrderByDescending(o => o.CreatedDate) .FirstOrDefault(); if (otpRecord == null) { // Check if OTP exists but expired or used var anyOtp = dbContext.OtpVerifications .Where(o => o.UserEmail == request.email && o.OtpCode == request.otpCode) .FirstOrDefault(); if (anyOtp != null) { if (anyOtp.IsUsed == true) { return DotnetLib.Http.HttpResponse.BuildResponse( log, url, json, CommonErrorCode.OtpAlreadyUsed, "OTP has already been used", new { } ); } if (anyOtp.ExpiredAt <= DateTime.Now) { return DotnetLib.Http.HttpResponse.BuildResponse( log, url, json, CommonErrorCode.OtpExpired, "OTP has expired", new { } ); } } return DotnetLib.Http.HttpResponse.BuildResponse( log, url, json, CommonErrorCode.OtpInvalid, "Invalid OTP", new { } ); } // Mark OTP as used otpRecord.IsUsed = true; // Get customer info var customer = dbContext.CustomerInfos .Where(c => c.Email == request.email) .FirstOrDefault(); if (customer == null) { return DotnetLib.Http.HttpResponse.BuildResponse( log, url, json, CommonErrorCode.UserNotFound, "Customer not found", new { } ); } // Update customer verification status customer.IsVerified = true; customer.LastLoginDate = DateTime.Now; customer.LastUpdate = DateTime.Now; // Generate JWT tokens int tokenExpireHours = 24; int refreshTokenExpireDays = 30; string accessToken = CommonLogic.GenToken(configuration, customer.Email ?? "", customer.Id.ToString() ?? ""); string refreshToken = CommonLogic.GenRefreshToken(configuration, customer.Email ?? ""); var expiresAt = DateTime.Now.AddHours(tokenExpireHours); var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays); // Revoke old tokens var oldTokens = dbContext.UserTokens .Where(t => t.CustomerId == customer.Id && t.IsRevoked == false) .ToList(); foreach (var oldToken in oldTokens) { oldToken.IsRevoked = true; } // Save new token var tokenId = (int)await Database.DbLogic.GenIdAsync(dbContext, "USER_TOKEN_SEQ"); var userToken = new UserToken { Id = tokenId, CustomerId = customer.Id.Value, AccessToken = accessToken, RefreshToken = refreshToken, TokenType = "Bearer", DeviceInfo = httpRequest.Headers["User-Agent"].ToString(), IpAddress = GetClientIpAddress(httpRequest), ExpiredAt = expiresAt, RefreshExpiredAt = refreshExpiresAt, IsRevoked = false, CreatedDate = DateTime.Now, LastUsed = DateTime.Now }; dbContext.UserTokens.Add(userToken); await dbContext.SaveChangesAsync(); return DotnetLib.Http.HttpResponse.BuildResponse( log, url, json, CommonErrorCode.Success, "Login successful", new { userId = customer.Id.Value, email = customer.Email ?? "", fullName = $"{customer.SurName} {customer.LastName}".Trim(), avatarUrl = customer.AvatarUrl, accessToken, refreshToken, expiresAt } ); } catch (Exception exception) { log.Error("Exception: ", exception); } return DotnetLib.Http.HttpResponse.BuildResponse( log, url, json, CommonErrorCode.SystemError, ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"), new { } ); } /// /// Generate 6-digit OTP code /// private string GenerateOtp() { using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create()) { var bytes = new byte[4]; rng.GetBytes(bytes); var number = Math.Abs(BitConverter.ToInt32(bytes, 0)) % 1000000; return number.ToString("D6"); } } /// /// Get client IP address /// private string GetClientIpAddress(HttpRequest httpRequest) { var ipAddress = httpRequest.Headers["X-Forwarded-For"].FirstOrDefault(); if (string.IsNullOrEmpty(ipAddress)) { ipAddress = httpRequest.HttpContext.Connection.RemoteIpAddress?.ToString(); } return ipAddress ?? "Unknown"; } #endregion } }