درود. من ارسال پیامک رو پیاده سازی کردم و یکی از دوستان سوالی در این رابطه پرسیده بود گفتم اینجا بزارم که به ایشون هم کمک کرده باشم.
1) ساخت مدل و ویو مدل های مربوطه
2) ثبت نام و گرفتم API از سایت اراعه دهنده پیامک (SMS.ir)
3) قرار دادن API در پروژه خود بر اساس معماری پروژه
در اینجا از API تستی (SandBox ) استفاده میکنم که برای تست کردن و پیدا کردن خطا ها و رفع اونهاست و پس از کلید ارسال تستی استفاده میکنیم .
برای واقعی کردن ارسال پیامک فقط کافیه کلیدی که بعد از خرید اشتراک (یا اشتراک رایگان که مقدارش 10 عدد پیامکه) دریافت کردید رو جایگزین اون کلید تستی کنید تا پیامک به صورت واقعی براتون ارسال بشه که بابت ارسال هر پیامک از شارژ شما کم میشه .
برای هر پروپرتی کامنت گذاشتم برای درک بهتر مطلب
مدل و ویو مدل های مورد نیاز :
مدل > سپس مایگریشن
این مدل برای ذخیره کردن موبایل و تاریخ انقاضای کد فعالسازی هستش.
در اینجا ما فقط شماره موبایل رو موقع ثبت نام میگیریم و چکش میکنیم و اگه تایید شد توسط کد ارسال، سپس ادامه ثبت نام رو به کاربر نشون میدیم .
public class SupplierRegisterTemp
{
public long Id { get; set; }
// شماره موبایل فروشنده
public string Mobile { get; set; }
// کد فعالسازی ارسال شده
public string ActivationCode { get; set; }
// زمان انقضای کد (مثلاً 5 دقیقه)
public DateTime ExpireDate { get; set; }
// آیا موبایل تأیید شده؟
public bool IsVerified { get; set; }
// زمان ایجاد رکورد
public DateTime CreatedAt { get; set; }
}ویو مدل های مورد نیاز برای پنل پیامکی برگرفته شده از خود SMS.ir :
public class VerifySendParameterModel
{
public string Name { get; set; }
public string Value { get; set; }
}
public class VerifySendModel
{
public string Mobile { get; set; }
public int TemplateId { get; set; }
public VerifySendParameterModel[] Parameters { get; set; }
}
public class VerifyCodeModel
{
public string Code { get; set; }
public string Mobile { get; set; }
}
بعد از ثبت نام در پنل پیامکی SMS.ir در بخش ارسال پیامک کد تایید میتونید بخش SandBox رو مشاهده کنید که نمونه کد در اختیار شما قرار میده ولی باید تغییراتی در ان اعمال کنید.
سرویس ارسال پیامک بستگی به معماری شما در پروژه :
public class SmsService : ISmsService
{
private readonly HttpClient _httpClient;
public SmsService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.DefaultRequestHeaders.Add("x-api-key", "iL64jrvhGIAWn4xfuUCr1UFjKgOmCYBlc1s9P");
}
public async Task<bool> SendActivationCodeAsync(string mobile, string code)
{
var model = new VerifySendModel()
{
Mobile = mobile,
TemplateId = 123456,
Parameters = new[]
{
new VerifySendParameterModel { Name = "CODE", Value = code }
}
};
string payload = JsonSerializer.Serialize(model);
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("https://api.sms.ir/v1/send/verify", content);
return response.IsSuccessStatusCode;
}
}در این بخش :
_httpClient.DefaultRequestHeaders.Add("x-api-key", "iL64jrvhGIAWn4xfuUCr1UFjKgOmCYBlc1s9P");این عبارت iL64jrvhGIAWn4xfuUCr1UFjKgOmCYBlc1s9P همون کلیدی هستش که دربارش گفتم شما باید کلیدی که خود در بخش sandBox در سایت دارید رو جایگزین این کلید من بکنید. (کلید من برای شما کار نخواهد کرد پس تست نکنید)
و همچنین پروپرتی ای هست به اسم TemplateId = 123456, این 123456 در واقع Id قالب پیامکتون هستش قالب پیامک همون ظاهر پیامکه که اگ قالب جدید بسازید باید id اون قالب رو از داخل سایت بزارید جای 123456.
در واقع 123456 قالب مخصوص SandBox هستش پس به این دست نزنید تا وقتی که کلید ارسال پیامک واقعی رو جایگزین کلید تستی کردید ، اون موقع میتونید قالب خودتون داخل همون ساستی بسازید و id شو بزارید جای 123456 .
مابقی سرویس ها :
فراخوانی سرویس ارسال پیامک و تولید کد تایید
public async Task SendActivationCode(string mobile)
{
var code = new Random().Next(100000, 999999).ToString();
var temp = new SupplierRegisterTemp
{
Mobile = mobile,
ActivationCode = code,
ExpireDate = DateTime.Now.AddMinutes(5),
IsVerified = false,
CreatedAt = DateTime.Now
};
await _genericTempRepository.CreateAsync(temp);
await _genericTempRepository.SaveAsync();
await _smsService.SendActivationCodeAsync(mobile, code);
}چک کردن اینکه ایا شماره تلفن و کد برای هم هستن ؟ و اینکه کد منقضی نشده؟ (ریپوزیتوری این بخش رو خودتون هم میتونید بنویسید و فقط یه چک کردن سادس) و دلیل اینکه bool بر نگردوندم برای انه که میخایم اگه وجود داشت بگیریمش و اپدیتش کنیم که isverified عش false بمونه یا true
public async Task<SupplierRegisterTemp> IsVerifyCodeExist(string code, string mobile)
{
return await _supplierAccountRepository.IsVerifyCodeExist(code,mobile);
}سرویس و ریپوزیتوری تایید کردن شماره تلفن که کد ارسالی درست بود:
سرویس
public async Task VerifyMobile(SupplierRegisterTemp model)
{
await _supplierAccountRepository.VerifyMobile(model);
}
ریپوزیتوری
public async Task VerifyMobile(SupplierRegisterTemp model)
{
model.IsVerified = true;
await context.SaveChangesAsync();
}در نهایت controller :
مجبورم کد کامل رو در اختیار قرار بدم و چون در سمت ویو به صورت ajax پیاده سازی شده سر همین کمی کنترلر فرق میکنه و ajax از سمت ویو کمی پیچیدش کرد ولی با کمی تامل درک خواهید کرد.
بیشتر اینجا مدیریت میکنیم که اگه فرد ثبت نام نصفه نیمه داشت و بعد از یه مدت دوباره اومد باید دوباره شماره تلفنش تایید بدشه پس باید اگه قبلا verify کرده بود دوباره باید غیر فعالش کنیم ازش کد تایید بگیریم تا مطمعن بشیم همون فرد و مالک شماره تلفن هست نه شخص دیگه. و سایر مدیریت های جلوگیری از سواستفاده در این بخش کامل نهانیده شده است .
[HttpPost]
[Route("Supplier/Register")]
public async Task<IActionResult> SendActivationCode([FromBody] SupplierRegisterTempViewModel model)
{
if (string.IsNullOrWhiteSpace(model.Mobile))
return Json(new { success = false, message = "شماره موبایل را وارد کنید" });
if (!Regex.IsMatch(model.Mobile, @"^09\d{9}$"))
return Json(new { success = false, message = "فرمت شماره همراه صحیح نمیباشد." });
if (string.IsNullOrWhiteSpace(model.CaptchaAnswer))
return Json(new { success = false, message = "عبارت امنیتی را وارد کنید" });
TempData.Peek("Captcha");
if (!Captcha.IsValid(model.CaptchaAnswer))
{
this.Captcha = new Captcha(140, 40, 20f, "#FFFFFF", "#6a11cb", Mode.Numeric);
TempData["Captcha"] = JsonConvert.SerializeObject(this.Captcha);
return Json(new
{
success = false,
captchaError = true,
message = "عبارت امنیتی صحیح نیست",
imageData = this.Captcha.ImageData
});
}
// ادامهٔ ثبتنام بدون تغییر
var existing = await _supplierAccountService.GetTempByMobile(model.Mobile);
string code = new Random().Next(1000, 9999).ToString();
if (existing != null && existing.IsVerified)
{
var supplier = await _supplierAccountService.GetSupplierByMobile(model.Mobile);
if (supplier != null)
return Json(new { success = false, message = "این شماره قبلاً ثبتنام شده است.", imageData = Captcha.ImageData });
await _supplierAccountService.UpdateTemp(existing, code);
bool smsSent = await _smsService.SendActivationCodeAsync(model.Mobile, code);
if (!smsSent)
return Json(new { success = false, message = "ارسال پیامک با خطا مواجه شد", imageData = Captcha.ImageData });
return Json(new { success = true, verified = true });
}
if (existing != null && !existing.IsVerified)
{
await _supplierAccountService.UpdateTemp(existing, code);
bool smsSent = await _smsService.SendActivationCodeAsync(model.Mobile, code);
if (!smsSent)
return Json(new { success = false, message = "ارسال پیامک با خطا مواجه شد", imageData = Captcha.ImageData });
return Json(new { success = true });
}
var temp = new SupplierRegisterTemp
{
Mobile = model.Mobile,
ActivationCode = code,
ExpireDate = DateTime.Now.AddMinutes(5),
IsVerified = false,
CreatedAt = DateTime.Now
};
await _supplierAccountService.SupplierRegisterTempsAddAsync(temp);
bool smsSentNew = await _smsService.SendActivationCodeAsync(model.Mobile, code);
if (!smsSentNew)
return Json(new { success = false, message = "ارسال پیامک با خطا مواجه شد", imageData = Captcha.ImageData });
return Json(new { success = true });
}
[HttpPost]
[Route("Supplier/VerifyCode")]
public async Task<IActionResult> VerifyCode([FromBody] VerifyCodeModel model)
{
var temp = await _supplierAccountService.IsVerifyCodeExist(model.Code, model.Mobile);
if (temp == null)
return Json(new { success = false, message = "کد وارد شده صحیح نیست یا منقضی شده است." });
await _supplierAccountService.VerifyMobile(temp);
TempData["SupplierMobile"] = model.Mobile;
return Json(new { success = true });
}
این هم ویو کامل :
@model SupplierRegisterTempViewModel
@{
Layout = "_SupplierAuthLayout";
ViewData["Title"] = "ثبت نام و همکاری";
}
<div class="seller-auth-wrapper">
<div class="seller-auth-box">
<!-- Left Side -->
<div class="seller-left ios-container">
<div id="page1" class="ios-page">
<div class="seller-logo">
<img src="/theme-assets/images/logo.svg" alt="" />
</div>
<h3 class="seller-title">ثبتنام فروشندگان</h3>
<p class="seller-desc">شماره موبایل خود را وارد کنید تا کد فعالسازی برایتان ارسال شود.</p>
<form id="mobileForm" class="seller-form">
<input asp-for="Mobile" name="mobile" class="form-control seller-input" placeholder="شماره موبایل" />
<span asp-validation-for="Mobile" class="text-danger small"></span>
<span class="text-danger" id="mobileError"></span>
<div class="register-field captcha-field">
<input asp-for="CaptchaAnswer" name="CaptchaAnswer" class="seller-input" placeholder="عبارت تصویر را وارد کنید">
<img id="captchaImage" src="@Model.ImageData" class="captcha-img" />
</div>
<span class="text-danger" id="capchaError"></span>
<button type="submit" class="seller-btn mt-3">ارسال کد فعالسازی</button>
</form>
<div class="seller-footer">
<a href="#" class="footer-link">راهنمای ثبتنام</a>
<span class="footer-divider">|</span>
<a href="#" class="footer-link">پشتیبانی فروشندگان</a>
</div>
</div>
</div>
<!-- Right Side -->
<div class="seller-right">
<img src="~/theme-assets/images/یه عکس مربع بزارید" class="seller-right-img" alt="Seller Illustration" />
<h3 class="seller-right-title">مرکز فروشندگان ---------</h3>
<p class="seller-right-desc">
با ثبتنام در مرکز فروشندگان، محصولات خود را در بزرگترین---- عرضه کنید.
</p>
</div>
</div>
</div>
<script>
document.getElementById("mobileForm").addEventListener("submit", function(e) {
e.preventDefault();
const submitBtn = document.querySelector(".seller-btn");
submitBtn.disabled = true;
submitBtn.innerHTML = "در حال ارسال...";
submitBtn.classList.add("loading");
let mobile = document.querySelector("input[name='mobile']").value.trim();
let captcha = document.querySelector("input[name='CaptchaAnswer']").value.trim();
let token = document.querySelector("input[name='__RequestVerificationToken']").value;
// ولیدیشن موبایل
document.getElementById("mobileError").innerText = "";
document.getElementById("capchaError").innerText = "";
if (mobile === "") {
document.getElementById("mobileError").innerText = "لطفا شماره همراه را وارد کنید";
resetButton();
return;
}
if (!/^09\d{9}$/.test(mobile)) {
document.getElementById("mobileError").innerText = "فرمت شماره همراه صحیح نمیباشد.";
resetButton();
return;
}
if (captcha === "") {
document.getElementById("capchaError").innerText = "لطفا عبارت امنیتی را وارد کنید";
resetButton();
return;
}
fetch("/Supplier/Register", {
method: "POST",
headers: {
"Content-Type": "application/json",
"RequestVerificationToken": token
},
body: JSON.stringify({ mobile: mobile, captchaAnswer: captcha })
})
.then(res => res.json())
.then(data => {
// کپچا اشتباه
if (data.captchaError) {
document.getElementById("capchaError").innerText = data.message;
document.getElementById("captchaImage").src = data.imageData;
resetButton();
return;
}
// سایر خطاها
if (!data.success) {
document.getElementById("capchaError").innerText = data.message;
resetButton();
return;
}
// موفق → صفحه بعد
const container = document.querySelector(".seller-left");
const oldPage = document.getElementById("page1");
const newPage = document.createElement("div");
newPage.className = "ios-page";
newPage.id = "page2";
// فرم دوم را کاملاً مخفی بساز
newPage.style.visibility = "hidden";
newPage.style.opacity = "0";
newPage.style.transform = "scale(0.98)";
newPage.style.filter = "blur(4px)";
newPage.style.transition = "all 0.45s ease";
newPage.innerHTML = `
<div class="seller-logo">
<img src="/theme-assets/images/alenda-logo.svg" alt="Alenda Tajhiz" />
</div>
<h3 class="seller-title">تأیید شماره موبایل</h3>
<p class="seller-desc">کد فعالسازی ارسال شد. لطفاً آن را وارد کنید.</p>
<form id="verifyForm" class="seller-form">
<input name="code" class="form-control seller-input" placeholder="کد فعالسازی" />
<span class="text-danger" id="codeError"></span>
<button type="submit" class="seller-btn mt-3">تأیید کد</button>
</form>
<input type="hidden" id="mobileHidden" value="${mobile}" />
<input type="hidden" id="tokenHidden" value="${token}" />
`;
// جایگزینی مستقیم فرم اول با فرم دوم (بدون پرش)
container.replaceChild(newPage, oldPage);
// اجرای انیمیشن بعد از یک فریم
requestAnimationFrame(() => {
newPage.style.visibility = "visible";
newPage.style.opacity = "1";
newPage.style.transform = "scale(1)";
newPage.style.filter = "blur(0)";
});
});
function resetButton() {
submitBtn.disabled = false;
submitBtn.innerHTML = "ارسال کد فعالسازی";
submitBtn.classList.remove("loading");
}
});
</script>
<script>
document.addEventListener("submit", function(e) {
if (e.target.id !== "verifyForm") return;
e.preventDefault();
let code = document.querySelector("input[name='code']").value;
let mobile = document.getElementById("mobileHidden").value;
let token = document.getElementById("tokenHidden").value;
fetch("/Supplier/VerifyCode", {
method: "POST",
headers: {
"Content-Type": "application/json",
"RequestVerificationToken": token
},
body: JSON.stringify({ code: code, mobile: mobile })
})
.then(res => res.json())
.then(data => {
if (!data.success) {
document.getElementById("codeError").innerText = data.message;
return;
}
window.location.href = "/Supplier/CompleteRegister";
});
});
</script>
برای css عش هم View رو به Copilot بدید تا بر اساس سلیقه خودتون براتون استایل ها و کلاس ها رو تغییر بده .
امیدوارم به دلیل حجم بالای پیام سرگیجه نگرفته باشید.
پایان)))))))))))
دقت کنید که با API مدل SandBox پیامک واقعی براتون ارسال نمیشه و فقط میتونید کد ارسالی به شماره تلفن رو از طریق بانک اطلاعتی چک کنید و توی بخش وارد کردن کد ارسالی وارد کنید تا عملیات تایید موبایل انجام بشه و برای ارسال پیامک واقعی باید اشتراک تهیه کنید یا از 10 عدد پیامک رایگان که بهتون تعلق میگیره با ثبت نام کردن در سایت ، استفاده کنید. با کلید مخصوص ارسال پیامک نه با کلید SandBox. بدورد
ممنون از شما