- Introduced a `tag` field in the `SmsLog` entity to categorize SMS messages. - Updated the `SmsService` to handle the new `tag` parameter during SMS dispatch. - Implemented a `SmsTextResolver` service to resolve SMS message templates based on tags. - Created a new `SmsMessageTemplate` entity for editable SMS templates with placeholders. - Added endpoints for managing SMS message templates in the admin panel. - Enhanced existing SMS dispatching methods across various controllers to utilize the tagging system. - Migrated the database to include the new `tag` field and created a seeding command for default SMS templates. - Updated admin API to filter SMS logs by tag and include tag information in responses.
12 KiB
SMS API
Prefix:
/api/v1/sms
Providers:kavenegar(default) |rangineh
All send operations are dispatched asynchronously via Symfony Messenger → Redis queue.
POST /api/v1/sms/send
Send a direct SMS message (free text).
Permission: ROLE_ADMIN
Request Body (application/json)
{
"mobile": "09123456789",
"message": "سلام، پیام آزمایشی",
"provider": "kavenegar"
}
| Field | Type | Required | Description |
|---|---|---|---|
mobile |
string | ✅ | Recipient mobile (09XXXXXXXXX) |
message |
string | ✅ | Message text |
provider |
string | ❌ | "kavenegar" or "rangineh" (default from env) |
Response 200
{
"success": true,
"data": { "message": "پیامک با موفقیت ارسال شد" }
}
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_001 |
401 | Missing token |
ERR_AUTH_006 |
403 | Not admin |
ERR_VALIDATION_001 |
422 | Invalid mobile format |
POST /api/v1/sms/send-template
Send an SMS using an approved template.
Permission: ROLE_ADMIN
Request Body (application/json)
{
"mobile": "09123456789",
"template_uuid": "tmpl-uuid-...",
"vars": {
"name": "دکتر علی احمدی",
"date": "۱۵ خرداد ۱۴۰۴"
},
"provider": "kavenegar"
}
| Field | Type | Required | Description |
|---|---|---|---|
mobile |
string | ✅ | Recipient mobile |
template_uuid |
string (UUID) | ✅ | UUID of an approved template |
vars |
object | ❌ | Key-value substitutions for template placeholders |
provider |
string | ❌ | Override provider |
Response 200
{
"success": true,
"data": { "message": "پیامک با موفقیت ارسال شد" }
}
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_001 |
401 | Missing token |
ERR_AUTH_006 |
403 | Not admin |
ERR_NOT_FOUND_001 |
404 | Template not found |
ERR_VALIDATION_001 |
422 | Template not approved |
POST /api/v1/sms/template
Create a new SMS template.
Permission: ROLE_ADMIN
Request Body (application/json)
{
"name": "تأیید نوبت",
"body": "دکتر گرامی ${name}، نوبت شما در تاریخ ${date} تأیید شد.",
"variables": ["name", "date"]
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✅ | Template display name |
body |
string | ✅ | Template text with ${variable} placeholders |
variables |
string[] | ❌ | List of expected variable names |
Response 201
{
"success": true,
"data": {
"uuid": "tmpl-uuid-...",
"name": "تأیید نوبت",
"body": "دکتر گرامی ${name}...",
"variables": ["name", "date"],
"status": "draft",
"created_at": 1717000000
}
}
Template Status Values:
| Value | Description |
|---|---|
draft |
Created, not submitted |
pending |
Submitted for review |
approved |
Ready to use |
rejected |
Rejected |
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_001 |
401 | Missing token |
ERR_AUTH_006 |
403 | Not admin |
ERR_VALIDATION_002 |
422 | Missing required field |
GET /api/v1/sms/template/{uuid}
Get template detail.
Permission: AUTH
Response 200
Template object.
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_001 |
401 | Missing token |
ERR_NOT_FOUND_001 |
404 | Template not found |
PATCH /api/v1/sms/template/{uuid}
Update a template (only allowed in draft or rejected status).
Permission: ROLE_ADMIN
Request Body (application/json)
{
"name": "تأیید نوبت - ویرایش",
"body": "نوبت شما در ${date} تأیید شد.",
"variables": ["date"]
}
All fields optional.
Response 200
Updated template object.
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_001 |
401 | Missing token |
ERR_AUTH_006 |
403 | Not admin |
ERR_NOT_FOUND_001 |
404 | Template not found |
ERR_SMS_003 |
422 | Template already submitted/approved |
POST /api/v1/sms/template/{uuid}/submit
Submit template for admin review (moves status from draft to pending).
Permission: ROLE_ADMIN
Response 200
Updated template with status: "pending".
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_001 |
401 | Missing token |
ERR_AUTH_006 |
403 | Not admin |
ERR_NOT_FOUND_001 |
404 | Template not found |
ERR_SMS_003 |
422 | Already submitted |
DELETE /api/v1/sms/template/{uuid}
Delete a template.
Permission: ROLE_ADMIN
Response 200
{ "success": true, "data": { "message": "قالب پیامک حذف شد" } }
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_AUTH_001 |
401 | Missing token |
ERR_AUTH_006 |
403 | Not admin |
ERR_NOT_FOUND_001 |
404 | Template not found |
GET /api/v1/admin/sms/templates
List all templates (admin view with all statuses).
Permission: ROLE_ADMIN
Response 200
{
"success": true,
"data": [
{
"uuid": "...",
"name": "تأیید نوبت",
"status": "approved",
"created_at": 1717000000
}
]
}
POST /api/v1/admin/sms/template/{uuid}/approve
Approve a pending template.
Permission: ROLE_ADMIN
Request Body (application/json)
{
"note": "تأیید شد",
"provider_code": "verify_appointment"
}
| Field | Type | Required | Description |
|---|---|---|---|
note |
string | ❌ | Admin note |
provider_code |
string | ❌ | Provider-side template code |
Response 200
Updated template with status: "approved".
POST /api/v1/admin/sms/template/{uuid}/reject
Reject a pending template.
Permission: ROLE_ADMIN
Request Body
{
"note": "متن قالب نامناسب است"
}
| Field | Type | Required |
|---|---|---|
note |
string | ✅ |
Response 200
Updated template with status: "rejected".
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_VALIDATION_002 |
422 | Missing note |
SMS Wallet
کیف پیامکی — جدا از کیف مالی، فقط برای ارسال پیامک.
GET /api/v1/sms/wallet/balance
Permission: IS_AUTHENTICATED_FULLY
{
"success": true,
"data": {
"balance_rials": 15000,
"sms_price_rials": 500,
"estimated_sms_count": 30
}
}
POST /api/v1/sms/wallet/charge
شارژ کیف پیامکی از طریق درگاه پرداخت.
Permission: IS_AUTHENTICATED_FULLY
{
"gateway": "mellat",
"amount_rials": 50000,
"frontend_address": "https://example.com/sms-wallet"
}
Response 200:
{
"success": true,
"data": {
"payment_uuid": "...",
"redirect_url": "https://gateway...",
"order_id": "ORD-..."
}
}
پس از پرداخت موفق، موجودی کیف خودکار شارژ میشود.
GET /api/v1/sms/wallet/logs
تراکنشهای کیف پیامک (paginated).
Query params: page, limit
{
"success": true,
"data": [
{
"uuid": "...",
"type": "credit",
"amount_rials": 50000,
"description": "شارژ کیف پیامک",
"created_at": 1718000000
}
],
"meta": { "totalRecords": 5, "totalPages": 1, "currentPage": 1 }
}
SMS Settings
GET /api/v1/sms/settings
تنظیمات پیامک entity جاری.
Permission: IS_AUTHENTICATED_FULLY
{
"success": true,
"data": {
"entity_type": "clinic",
"entity_id": 5,
"reminder_enabled": true,
"reminder_hours_before": 2,
"post_visit_enabled": false,
"post_visit_text": null,
"post_visit_text_pending": null,
"post_visit_text_status": "none",
"post_visit_text_reject_reason": null,
"updated_at": 1718000000
}
}
PATCH /api/v1/sms/settings
Permission: IS_AUTHENTICATED_FULLY
{
"reminder_enabled": true,
"reminder_hours_before": 3,
"post_visit_enabled": true,
"post_visit_text": "از مراجعه شما سپاسگزاریم"
}
تغییر رفتار post_visit_text: متن ارسالشده مستقیماً اعمال نمیشود — در فیلد post_visit_text_pending ذخیره میشود و وضعیت post_visit_text_status به pending تغییر میکند. پس از تأیید ادمین، به post_visit_text منتقل میشود.
مقادیر post_visit_text_status: none | pending | approved | rejected
Admin Endpoints
GET /api/v1/admin/sms/settings/review
Permission: ROLE_ADMIN — لیست همه تنظیمات SMS با وضعیت pending
{
"success": true,
"data": {
"data": [
{
"id": 3,
"entity_type": "doctor",
"entity_id": 7,
"post_visit_text_pending": "متن در انتظار تأیید",
"post_visit_text_status": "pending",
...
}
]
}
}
POST /api/v1/admin/sms/settings/{id}/approve
Permission: ROLE_ADMIN — تأیید متن پیامک. post_visit_text_pending به post_visit_text منتقل میشود.
POST /api/v1/admin/sms/settings/{id}/reject
Permission: ROLE_ADMIN — رد متن پیامک.
{ "reason": "متن نامناسب است" }
GET /api/v1/admin/sms/wallet-report
Permission: ROLE_ADMIN — لیست همه کیفهای پیامکی (paginated)
متن ویرایشپذیر پیامکهای سیستمی
متن پیامکهای سیستمی (OTP، پرداخت، دعوت کلینیک، پیشثبتنام، تأیید موبایل) از پنل قابل ویرایش است و بر اساس تگ کلیددار میشود. هر متن placeholderهای مجاز خود را دارد (مثل {code}، {doctor}، {date}). هنگام ارسال، SmsTextResolver متنِ ویرایششدهی DB را میگیرد و placeholderها را جایگزین میکند؛ اگر رکوردی نبود به متن پیشفرض fallback میشود.
تگها:
otp،payment،clinic_invitation،pre_registration،notification_mobile. (پیامک قالبیِ کاربر با تگuser_templateجداگانه از طریقPOST /api/v1/sms/templateمدیریت میشود.)
GET /api/v1/admin/sms/messages
لیست همهی متنهای سیستمی (تگهایی که هنوز رکورد ندارند با مقدار پیشفرض برگردانده میشوند).
Permission: ROLE_ADMIN
Response 200
{
"success": true,
"data": {
"data": [
{
"tag": "otp",
"title": "کد تأیید ورود",
"body": "کد تأیید شما: {code}",
"variables": ["code"],
"updated_at": 1718000000
}
]
}
}
PATCH /api/v1/admin/sms/messages/{tag}
ویرایش متن یک پیامک سیستمی.
Permission: ROLE_ADMIN
Path Parameters
| Param | Type | Description |
|---|---|---|
tag |
string | یکی از تگهای سیستمی |
Request Body
{ "body": "کد ورود شما: {code}" }
| Field | Type | Required | Description |
|---|---|---|---|
body |
string | ✅ | متن جدید؛ فقط placeholderهای مجازِ همان تگ پذیرفته میشود |
Response 200
{ "success": true, "data": { "data": { "tag": "otp", "title": "...", "body": "...", "variables": ["code"], "updated_at": 1718000123 } } }
Errors
| Code | HTTP | Description |
|---|---|---|
ERR_NOT_FOUND_001 |
404 | تگ ناشناخته |
ERR_VALIDATION_002 |
422 | متن خالی |
ERR_VALIDATION_001 |
422 | placeholder نامعتبر (خارج از متغیرهای مجاز تگ) |
Command:
php bin/console app:seed-sms-message-templatesرکوردهای پیشفرض را برای تگهایی که هنوز ندارند میسازد.