feat: update SMS templates and logic to ensure required tokens are present for Kavenegar integration
This commit is contained in:
+6
-6
@@ -485,7 +485,7 @@ Updated template with `status: "rejected"`.
|
||||
**همهی پیامکهای سیستمی از طریق Kavenegar VerifyLookup (`verify/lookup.json`) ارسال میشوند** (نه متنآزاد `sms/send`). هر تمپلت دو فیلد اضافه دارد:
|
||||
|
||||
- `kavenegar_template`: نام تمپلت مصوب در پنل کاوهنگار. **متن واقعیِ ارسالی از همین تمپلت پنل میآید، نه از `body` دیتابیس**؛ `body` فقط برای پیشنمایش ادمین و رندرِ رکورد `SmsLog` استفاده میشود و باید دستی با تمپلت پنل همراستا نگه داشته شود.
|
||||
- `token_map`: نگاشت متغیر منطقی → جایگاه کاوهنگار. قانون فاصله: `token`/`token2`/`token3` **فاصله نمیپذیرند**؛ `token10`/`token20` فاصله مجازند. پس مقادیر دارای فاصله (نام دکتر/بیمار/کلینیک/سایت) در `token10`/`token20`.
|
||||
- `token_map`: نگاشت متغیر منطقی → جایگاه کاوهنگار. **قانون کاوهنگار:** هر الگو **حتماً باید `%token` داشته باشد** (token1 اجباری است)؛ پس token_map هر تگ یک مقدار به `token` میدهد. قانون فاصله: `token`/`token2`/`token3` **فاصله نمیپذیرند** — provider (`KavehNegarProvider`) فاصلههای مقدارِ این جایگاهها را خودکار با نیمفاصله (ZWNJ) جایگزین میکند؛ `token10`/`token20` فاصله را بدون تغییر میپذیرند. پس ترجیحاً مقادیر بدونفاصله (کد/تاریخ/ساعت/username/لینک) در `token`/`token2`/`token3` و مقادیر دارای فاصله (نام دکتر/بیمار/کلینیک) در `token10`/`token20`.
|
||||
|
||||
`SmsService::dispatchTemplate(tag, mobile, vars)` نقطهی واحدِ ارسال است: `kavenegar_template` و `token_map` را از رکورد DB (یا `SmsMessageTemplate::DEFAULTS`) میخواند، `vars` را به جایگاهها نگاشت میکند و با VerifyLookup میفرستد. اگر `kavenegar_template` تعریف نشده باشد → fallback به ارسال متنآزاد با `body` رندرشده.
|
||||
|
||||
@@ -495,12 +495,12 @@ Updated template with `status: "rejected"`.
|
||||
|----|--------------------|-----------|
|
||||
| `otp` | `clinicpro-otp` | code→token, site→token10 |
|
||||
| `notification_mobile` | `clinicpro-notify-code` | code→token |
|
||||
| `payment` | `clinicpro-payment` | doctor→token10, date→token20 |
|
||||
| `clinic_invitation` | `clinicpro-clinic-invite` | clinic→token10, link→token |
|
||||
| `payment` | `clinicpro-payment` | date→token, time→token2, doctor→token10 |
|
||||
| `clinic_invitation` | `clinicpro-clinic-invite` | link→token, clinic→token10 |
|
||||
| `pre_registration` | `clinicpro-pre-register` | username→token, password→token2, link→token3 |
|
||||
| `secretary` | `clinicpro-secretary` | owner→token10, username→token, link→token3 |
|
||||
| `doctor_appointment` | `clinicpro-doctor-appt` | patient→token10, date→token2, time→token3 |
|
||||
| `welcome` | `clinicpro-welcome` | name→token10, site→token20 |
|
||||
| `secretary` | `clinicpro-secretary` | username→token, link→token3, owner→token10 |
|
||||
| `doctor_appointment` | `clinicpro-doctor-appt` | date→token, time→token2, patient→token10 |
|
||||
| `welcome` | `clinicpro-welcome` | name→token, site→token10 |
|
||||
|
||||
> **پیشنیاز:** این تمپلتها باید در پنل کاوهنگار ساخته و تأیید شوند (نیازمند اشتراک advanced). تمپلتهای دارای `{link}` باید با تأیید لینک ساخته شوند.
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* اصلاح token_map تمپلتهای پیامک تا هرکدام حتماً %token داشته باشند (الزام VerifyLookup کاوهنگار).
|
||||
* payment/doctor_appointment/welcome قبلاً token نداشتند.
|
||||
*/
|
||||
final class Version20260705081716 extends AbstractMigration
|
||||
{
|
||||
private const FIX = [
|
||||
'payment' => ['{"date":"token","time":"token2","doctor":"token10"}', 'نوبت شما با {doctor} در تاریخ {date} ساعت {time} ثبت و تأیید شد.'],
|
||||
'doctor_appointment' => ['{"date":"token","time":"token2","patient":"token10"}', null],
|
||||
'welcome' => ['{"name":"token","site":"token10"}', null],
|
||||
];
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'token_map پیامکها: تضمین وجود %token در payment/doctor_appointment/welcome';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
foreach (self::FIX as $tag => [$map, $body]) {
|
||||
if ($body !== null) {
|
||||
$this->addSql(
|
||||
'UPDATE sms_message_templates SET token_map = :map, body = :body WHERE tag = :tag',
|
||||
['map' => $map, 'body' => $body, 'tag' => $tag]
|
||||
);
|
||||
} else {
|
||||
$this->addSql(
|
||||
'UPDATE sms_message_templates SET token_map = :map WHERE tag = :tag',
|
||||
['map' => $map, 'tag' => $tag]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql("UPDATE sms_message_templates SET token_map = '{\"doctor\":\"token10\",\"date\":\"token20\"}', body = 'نوبت شما با {doctor} در تاریخ {date} ثبت و تأیید شد.' WHERE tag = 'payment'");
|
||||
$this->addSql("UPDATE sms_message_templates SET token_map = '{\"patient\":\"token10\",\"date\":\"token2\",\"time\":\"token3\"}' WHERE tag = 'doctor_appointment'");
|
||||
$this->addSql("UPDATE sms_message_templates SET token_map = '{\"name\":\"token10\",\"site\":\"token20\"}' WHERE tag = 'welcome'");
|
||||
}
|
||||
}
|
||||
@@ -312,10 +312,10 @@ final class PaymentManager
|
||||
|
||||
$mobile = $appointment->getPatientMobile();
|
||||
if ($mobile) {
|
||||
$when = $this->jalali->formatDateTime($appointment->getSlotStart());
|
||||
$this->smsService->dispatchTemplate(SmsLog::TAG_PAYMENT, $mobile, [
|
||||
'doctor' => $doctor->getName(),
|
||||
'date' => $when,
|
||||
'date' => $this->jalali->formatDateTime($appointment->getSlotStart(), false),
|
||||
'time' => date('H:i', $appointment->getSlotStart()),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,10 @@ class SmsMessageTemplate
|
||||
],
|
||||
SmsLog::TAG_PAYMENT => [
|
||||
'title' => 'تأیید پرداخت و نوبت',
|
||||
'body' => 'نوبت شما با {doctor} در تاریخ {date} ثبت و تأیید شد.',
|
||||
'variables' => ['doctor', 'date'],
|
||||
'body' => 'نوبت شما با {doctor} در تاریخ {date} ساعت {time} ثبت و تأیید شد.',
|
||||
'variables' => ['doctor', 'date', 'time'],
|
||||
'kavenegar_template' => 'clinicpro-payment',
|
||||
'token_map' => ['doctor' => 'token10', 'date' => 'token20'],
|
||||
'token_map' => ['date' => 'token', 'time' => 'token2', 'doctor' => 'token10'],
|
||||
],
|
||||
SmsLog::TAG_CLINIC_INVITATION => [
|
||||
'title' => 'دعوت پزشک به کلینیک',
|
||||
@@ -67,14 +67,14 @@ class SmsMessageTemplate
|
||||
'body' => "نوبت جدید ثبت شد.\nبیمار: {patient}\nتاریخ: {date} ساعت {time}\n\nبرای مدیریت نوبتهای خود بهصورت رایگان به سایت clinic-pro.ir مراجعه کنید.\nکلینیک پرو",
|
||||
'variables' => ['patient', 'date', 'time'],
|
||||
'kavenegar_template' => 'clinicpro-doctor-appt',
|
||||
'token_map' => ['patient' => 'token10', 'date' => 'token2', 'time' => 'token3'],
|
||||
'token_map' => ['date' => 'token', 'time' => 'token2', 'patient' => 'token10'],
|
||||
],
|
||||
SmsLog::TAG_WELCOME => [
|
||||
'title' => 'خوشآمدگویی',
|
||||
'body' => '{name} عزیز، به {site} خوش آمدید. پروفایل شما توسط نماینده ثبت شد.',
|
||||
'variables' => ['name', 'site'],
|
||||
'kavenegar_template' => 'clinicpro-welcome',
|
||||
'token_map' => ['name' => 'token10', 'site' => 'token20'],
|
||||
'token_map' => ['name' => 'token', 'site' => 'token10'],
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -43,6 +43,17 @@ class KavehNegarProvider implements SmsProviderInterface
|
||||
}
|
||||
}
|
||||
|
||||
// token/token2/token3 مقدارِ دارای فاصله را رد میکنند؛ فاصله را با نیمفاصله (ZWNJ) جایگزین میکنیم.
|
||||
private const NO_SPACE_SLOTS = ['token', 'token2', 'token3'];
|
||||
|
||||
private function forSlot(string $slot, string $value): string
|
||||
{
|
||||
if (!in_array($slot, self::NO_SPACE_SLOTS, true)) {
|
||||
return $value;
|
||||
}
|
||||
return preg_replace('/\s+/u', "\u{200C}", $value) ?? $value;
|
||||
}
|
||||
|
||||
public function sendTemplate(string $mobile, string $templateCode, array $vars): bool
|
||||
{
|
||||
try {
|
||||
@@ -54,11 +65,12 @@ class KavehNegarProvider implements SmsProviderInterface
|
||||
if ($vars !== [] && array_keys($vars) !== range(0, count($vars) - 1)
|
||||
&& array_diff(array_keys($vars), $slots) === []) {
|
||||
foreach ($vars as $slot => $v) {
|
||||
$params[$slot] = $v;
|
||||
$params[$slot] = $this->forSlot($slot, (string) $v);
|
||||
}
|
||||
} else {
|
||||
foreach (array_values($vars) as $i => $v) {
|
||||
$params['token' . ($i > 0 ? $i + 1 : '')] = $v;
|
||||
$slot = 'token' . ($i > 0 ? $i + 1 : '');
|
||||
$params[$slot] = $this->forSlot($slot, (string) $v);
|
||||
}
|
||||
}
|
||||
$resp = $this->httpClient->request('POST',
|
||||
|
||||
Reference in New Issue
Block a user