- Added support for sending OTP messages using Kavenegar's VerifyLookup method, ensuring compliance with specified token formatting and template usage. - Updated OtpService to handle new template parameters and fallback mechanisms. - Introduced ImageCropModal component for cropping images with a user-friendly interface. - Created utility function for cropping images and generating downloadable files.
11 KiB
ارسال پیامک OTP فقط از طریق Kavenegar VerifyLookup (پترن)
پروژه
clinicpro (Backend — SMS/Auth)
زمینه
پیامک کد تأیید ورود (OTP) الان با متد متنآزاد send() کاوهنگار (/sms/send.json) ارسال میشود. کاوهنگار برای ارسال کد تأیید، متد اختصاصی VerifyLookup (/verify/lookup.json) دارد که با پترن مصوب کار میکند، روی خطوط اشتراکی هم تحویل مطمئنتری دارد و برای OTP توصیه/لازم است. هدف: فقط OTP از این به بعد از طریق Lookup ارسال شود (بقیه پیامکها — welcome، secretary، doctor_appointment، user_template — دستنخورده بمانند).
اسپک Kavenegar VerifyLookup (از داکیومنت رسمی)
- Endpoint:
https://api.kavenegar.com/v1/{API-KEY}/verify/lookup.json(GET/POST) - پارامترهای اجباری:
receptor(موبایل)،token(مقدار کد؛ max 100؛ بدون فاصله؛ بدون آندرلاین/خط جدید)،template(نام پترن مصوب در پنل) - پارامترهای اختیاری توکن و قانون فاصله (بحرانی):
توکن فاصله token,token2,token3بدون فاصله (رد میشود) token10حداکثر ۵ فاصله مجاز token20حداکثر ۸ فاصله مجاز type:sms(پیشفرض) یاcall.- پترن باید از قبل در پنل کاوهنگار تأیید شده باشد؛ متنِ پیام روی پنل ثابت است (نه از DB).
- نیازمند اشتراک advanced.
پیامد: کد ۵ رقمی (بدون فاصله) → token. اسم سایت مثل «یاسوج نوبت» (۱ فاصله) → باید در token10 برود (نه token/token2/token3 که فاصله را رد میکنند).
فایلهای مرتبط
| فایل | نقش |
|---|---|
src/Auth/Service/OtpService.php |
ساخت و dispatch پیامک OTP — باید به Lookup سوییچ شود |
src/Sms/Service/SmsService.php |
dispatchAsync(...templateCode, templateVars...) → اگر templateCode باشد sendTemplate() صدا میزند |
src/Sms/Provider/KavehNegarProvider.php |
sendTemplate() → /verify/lookup.json؛ map توکن باید نامدار شود (برای token10) |
.env + config/services.yaml |
افزودن KAVENEGAR_OTP_TEMPLATE و bind به OtpService |
docs/api/sms.md |
مستندسازی سوییچ OTP به Lookup |
وضعیت فعلی
OtpService::sendCode (متنآزاد، بدون templateCode):
if ($this->appEnv !== 'dev') {
$site = $this->resolveSiteName($domain);
$message = $this->smsText->resolve(SmsLog::TAG_OTP, ['code' => $code, 'site' => $site]);
$this->sms->dispatchAsync($mobile, $message, tag: SmsLog::TAG_OTP);
}
Constructor فعلی:
public function __construct(
private readonly CacheInterface $cache,
private readonly SmsService $sms,
private readonly SmsTextResolver $smsText,
private readonly CityRepository $cityRepo,
private readonly int $otpTtl = 1200,
private readonly string $appEnv = 'dev',
) {}
SmsService::dispatchAsync / sendNow (مسیر انتخاب send vs lookup):
public function dispatchAsync(
string $mobile, string $message, string $provider = 'kavenegar',
?string $templateUuid = null, array $templateVars = [],
?string $templateCode = null, string $tag = SmsLog::TAG_GLOBAL,
): void { /* dispatch SendSmsMessage */ }
// sendNow:
$success = ($msg->templateCode !== null)
? $provider->sendTemplate($msg->mobile, $msg->templateCode, $msg->templateVars)
: $provider->send($msg->mobile, $msg->message);
KavehNegarProvider::sendTemplate (map ترتیبی فعلی — فاصله را در token2/3 میگذارد که رد میشود):
public function sendTemplate(string $mobile, string $templateCode, array $vars): bool
{
try {
$params = ['receptor' => $mobile, 'template' => $templateCode];
foreach (array_values($vars) as $i => $v) {
$params['token' . ($i > 0 ? $i + 1 : '')] = $v;
}
$resp = $this->httpClient->request('POST',
self::BASE . '/' . $this->key() . '/verify/lookup.json', [
'body' => http_build_query($params), 'timeout' => 10,
]);
$data = $resp->toArray();
return ($data['return']['status'] ?? 0) === 200;
} catch (\Throwable $e) { /* log; return false */ }
}
caller دیگر sendTemplate: فقط POST /api/v1/sms/send-template در src/Sms/Controller/SmsController.php:488 که $vars را با کلیدهای معنایی (مثل name,date) و وابسته به ترتیب پاس میدهد. پس map ترتیبی نباید برای این caller بشکند.
.env فعلی:
OTP_TTL=1200
KAVENEGAR_API_KEY=change_me
وظایف
۱. KavehNegarProvider::sendTemplate — پشتیبانی توکنِ نامدار (backward-safe)
اگر همهی کلیدهای $vars نام slot معتبر کاوهنگار باشند (token,token2,token3,token10,token20) → همانها را مستقیم استفاده کن (تا اسم سایتِ فاصلهدار در token10 برود). در غیر این صورت (کلیدهای معنایی/لیستی) → همان map ترتیبی فعلی (برای caller send-template).
$params = ['receptor' => $mobile, 'template' => $templateCode];
$slots = ['token', 'token2', 'token3', 'token10', 'token20'];
if ($vars !== [] && array_keys($vars) !== range(0, count($vars) - 1)
&& array_diff(array_keys($vars), $slots) === []) {
foreach ($vars as $slot => $v) { $params[$slot] = $v; }
} else {
foreach (array_values($vars) as $i => $v) {
$params['token' . ($i > 0 ? $i + 1 : '')] = $v;
}
}
۲. .env + config/services.yaml — نام پترن OTP
.env (کاربر مقدار واقعی پترن مصوب را میگذارد):
KAVENEGAR_OTP_TEMPLATE=
config/services.yaml روی سرویس OtpService:
App\Auth\Service\OtpService:
arguments:
$otpTtl: '%env(int:OTP_TTL)%'
$appEnv: '%kernel.environment%'
$otpTemplate: '%env(default::KAVENEGAR_OTP_TEMPLATE)%'
۳. OtpService — dispatch از طریق Lookup
- پارامتر constructor جدید
?string $otpTemplate = nullبعد از پارامترهای بدوندیفالت و کنار$appEnv(ترتیب معتبر PHP؛ همه دیفالتدار در انتها). - در
sendCode، وقتی پترن ست است → باtemplateCodeو توکنهای نامدار dispatch کن:
if ($this->appEnv !== 'dev') {
$site = $this->resolveSiteName($domain);
$message = $this->smsText->resolve(SmsLog::TAG_OTP, ['code' => $code, 'site' => $site]); // فقط برای لاگ SmsLog
if ($this->otpTemplate) {
$this->sms->dispatchAsync(
$mobile, $message,
templateCode: $this->otpTemplate,
templateVars: ['token' => $code, 'token10' => $site],
tag: SmsLog::TAG_OTP,
);
} else {
// fallback متنآزاد فقط وقتی پترن ست نشده (مثلاً محیط توسعه)
$this->sms->dispatchAsync($mobile, $message, tag: SmsLog::TAG_OTP);
}
}
token= کد ۵ رقمی (بدون فاصله ✓).token10= اسم سایت (تا ۵ فاصله مجاز ✓).- اگر پترن مصوب فقط
%tokenداشته باشد،token10اضافی توسط کاوهنگار نادیده گرفته میشود (بیخطر) — اسم سایت فقط وقتی نمایش داده میشود که پترن%token10هم داشته باشد.
۴. مستندسازی docs/api/sms.md
- در بخش «متن ویرایشپذیر پیامکهای سیستمی»، تگ
otp: تصریح کن که ارسال OTP از طریق Kavenegar VerifyLookup انجام میشود (نهsendمتنآزاد) وقتیKAVENEGAR_OTP_TEMPLATEست باشد؛ متنِ پیام از پترن مصوب کاوهنگار میآید نه از body قابلویرایش DB (body صرفاً برای لاگSmsLog). - map توکنها را مستند کن:
token= کد،token10= اسم سایت (فاصلهدار). - الزام
.env:KAVENEGAR_OTP_TEMPLATEباید نام پترن مصوب باشد؛ بدون آن، fallback به متنآزاد.
نکات مهم
- فقط OTP به Lookup میرود؛ مسیر
send()متنآزاد برای بقیهی تگها و callersend-templateدستنخورده بماند. - قانون فاصله رعایت شود: کد →
token؛ هر مقدار فاصلهدار (اسم سایت) →token10. هرگز اسم فاصلهدار درtoken/token2/token3نگذار. - پترن باید در پنل کاوهنگار مصوب باشد و ساختار توکنش با map کد بخواند (
%tokenبرای کد، در صورت نیاز%token10برای سایت). این خارج از کد است و باید توسط صاحب حساب انجام شود. - نیازمند اشتراک advanced کاوهنگار.
- backward-safe: تغییر
sendTemplateنباید callersend-template(کلیدهای معنایی/ترتیبی) را بشکند — با شرط «همه کلیدها slot معتبرند» تضمین شود. - cache: بعد از تغییر constructor
OtpServiceو bind جدید،cache:clearبرای هر دو محیطdevوtestلازم است (وگرنه کانتینر کامپایلشدهی قدیمی TypeError میدهد). worker پیامک (messenger:consume async) هم باید ریاستارت شود. - تست:
(تست واقعی ارسال Lookup نیاز به API key و پترن مصوب دارد؛ در
ddev exec php -l src/Auth/Service/OtpService.php ddev exec php -l src/Sms/Provider/KavehNegarProvider.php ddev exec php bin/console cache:clear ddev exec php bin/console cache:clear --env=test ddev exec php bin/console lint:container ddev exec php bin/phpunit tests/Auth/SendCodeMobileRateLimitTest.phpdev/testپیامک ارسال نمیشود.) - بعد از تغییر،
docs/api/sms.mdرا در همان session بهروز کن (قانون استاندینگ پروژه).