Files
customerTicket/EXTERNAL_CONTACT_APIS.md
2026-09-30 08:39:26 +03:30

180 lines
18 KiB
Markdown

# اتصال مخاطبان CRM و سرویس‌های بیرونی به Phone
این راهنما هم برای پیاده‌سازی endpoint جست‌وجو در CRM است و هم برای تنظیم آن در `/x5admin` ← «منابع بیرونی». Phone هنگام جست‌وجو از سرور خود به API درخواست می‌فرستد، پاسخ JSON را به نام و شماره‌های قابل تماس تبدیل می‌کند و نتیجه را در «مخاطبان» نشان می‌دهد. مخاطب بیرونی به دفترچهٔ محلی کپی نمی‌شود.
## قرارداد پیشنهادی برای تیم CRM
ساده‌ترین قرارداد، یک endpoint خواندنی با پاسخ JSON است:
```http
GET /api/phone-contacts?search=%D8%B9%D9%84%DB%8C&limit=50 HTTP/1.1
Host: crm.example.com
Authorization: Bearer <service-token>
Accept: application/json
```
```json
{
"contacts": [
{
"name": "علی رضایی",
"phones": [
{"type": "همراه", "number": "09123456789"},
{"type": "دفتر", "number": "02112345678"}
]
}
]
}
```
CRM برای نبود نتیجه `{"contacts":[]}` با کد موفقیت `2xx` برگرداند. برای احراز هویت نامعتبر `401/403` و برای خطای سرویس کد `5xx` مناسب است. پاسخ باید JSON معتبر باشد؛ Phone متن یا XML را تفسیر نمی‌کند. CRM باید نتایج مرتبط را در **همان پاسخ اول** برگرداند، چون Phone صفحه‌بندی API بیرونی را دنبال نمی‌کند.
| ویژگی پاسخ | رفتار Phone |
| --- | --- |
| نام | متن غیرخالی؛ فاصلهٔ ابتدا و انتها حذف و طول نمایش به ۱۲۰ نویسه محدود می‌شود. |
| شماره | به متن تبدیل می‌شود؛ فاصله، پرانتز، نقطه و خط تیره حذف و رقم‌های فارسی/عربی لاتین می‌شوند. حاصل باید فقط رقم، `+`، `*` یا `#` و حداکثر ۶۴ نویسه باشد. |
| نتیجهٔ بدون شمارهٔ معتبر | نمایش داده نمی‌شود. شماره‌های تکراری یک مخاطب فقط یک‌بار نمایش داده می‌شوند. |
| سقف پردازش | ۵۰ مورد نخست هر پاسخ، تا ۵ شماره برای هر مخاطب، حداکثر ۱ MiB برای کل پاسخ. |
| زمان پاسخ | مهلت هر منبع در پنل ۱ تا ۸ ثانیه است؛ مقدار پیش‌فرض ۵ ثانیه. |
شناسهٔ مخاطب CRM برای این قابلیت لازم نیست. Phone شمارهٔ `0912...` را خودکار به `+98912...` تبدیل نمی‌کند؛ CRM باید شماره را در قالب قابل شماره‌گیری PBX بدهد.
## نمونهٔ پیاده‌سازی endpoint در CRM
قرارداد بالا به زبان یا فریم‌ورک خاصی وابسته نیست. منطق endpoint می‌تواند چنین باشد:
```text
GET /api/phone-contacts?search=<text>&limit=50
1. Authorization: Bearer <service-token> را بررسی کن.
2. متن search را trim و طول آن را محدود کن؛ برای متن کوتاه‌تر از ۲ نویسه نتیجهٔ خالی بده.
3. جست‌وجو را با query پارامتری در دیتابیس CRM اجرا کن؛ رکوردهایی را که نباید
برای کاربران Phone قابل مشاهده باشند، همین‌جا حذف کن.
4. نام و آرایهٔ شماره‌ها را در JSON قرارداد بالا برگردان؛ حداکثر ۵۰ نتیجه.
```
اگر در SQL از `LIKE` استفاده می‌کنید، مقدار ورودی را مستقیم وارد متن SQL نکنید؛ پارامتر bind کنید و اگر `%` و `_` نباید wildcard باشند، آن‌ها را مطابق موتور دیتابیس escape کنید. جست‌وجوی نام فارسی، شکل‌های متفاوت «ی/ي» و «ک/ك» و ترتیب نتایج را در CRM حل کنید؛ Phone فقط رشتهٔ واردشده را ارسال می‌کند. برای کارایی، ایندکس و حد نتیجه در CRM بگذارید. اگر API موجود ساختار دیگری دارد، به کمک نگاشت پنل آن را تطبیق دهید یا یک endpoint واسط JSON بسازید.
## تنظیم قدم‌به‌قدم در x5admin
1. در `/x5admin`، «منابع بیرونی» ← «افزودن منبع» را باز کنید. ابتدا گزینهٔ «فعال برای جست‌وجوی کاربران» را خاموش نگه دارید.
2. «نام منبع» یکتا و ۲ تا ۸۰ نویسه، مانند «CRM فروش»، تعیین کنید. این نام کنار نتایج دیده می‌شود.
3. روش `GET` یا `POST` و «آدرس ثابت API» را وارد کنید. آدرس باید کامل و به‌طور پیش‌فرض `https://` باشد؛ `{{query}}` را در خود آدرس نگذارید.
4. «پارامترهای URL»، «هدرها» و «بدنهٔ POST» را به شکل **شیء JSON معتبر** وارد کنید؛ مقدار خالی `{}` است. دست‌کم یک مقدار در پارامترها یا بدنه باید `{{query}}` داشته باشد.
5. برای `GET`، بدنه را `{}` بگذارید. برای `POST`، فرمت `JSON` یا `فرم` را مطابق CRM انتخاب کنید. پارامترهای URL در هر دو روش قابل استفاده‌اند.
6. «مسیر فهرست»، «مسیر نام مخاطب» و «نگاشت شماره‌ها» را از روی نمونهٔ **پاسخ واقعی** CRM تکمیل کنید. برای قرارداد ابتدای راهنما: `contacts`، `name` و `[{"path":"phones[]","value_path":"number","label_path":"type"}]`.
7. منبع را ذخیره کنید. برای آزمون واقعی، آن را فعال کنید و در `/phone` نام شناخته‌شده‌ای را جست‌وجو کنید. فعال‌سازی بلافاصله اعمال می‌شود و راه‌اندازی دوباره لازم ندارد.
در نسخهٔ فعلی، پنل دکمهٔ «آزمایش درخواست» ندارد؛ ذخیرهٔ منبع غیرفعال، درخواستی به CRM نمی‌فرستد. برای بررسی مستقل، درخواست نمونه را با `curl` از **میزبان اجرای Phone** به CRM بفرستید. نمونه در بخش اعتبارنامه آمده است.
### معنی فیلدهای درخواست
| فیلد پنل | نمونه | توضیح |
| --- | --- | --- |
| آدرس ثابت API | `https://crm.example.com/api/phone-contacts` | مقصد ثابت؛ جای‌نگهدار جست‌وجو/رمز، نام کاربری یا رمز داخل URL و fragment پذیرفته نمی‌شود. |
| روش درخواست | `GET` / `POST` | سایر روش‌ها پشتیبانی نمی‌شوند. |
| پارامترهای URL | `{"search":"{{query}}","limit":50}` | به query string اضافه و توسط HTTP client کدگذاری می‌شوند. |
| هدرها | `{"Authorization":"Bearer {{env:PHONE_EXTERNAL_CRM_TOKEN}}"}` | هدرهای ثابت یا ارجاع به متغیر محیطی؛ `{{query}}` در هدر مجاز نیست. |
| بدنهٔ POST | `{"filter":{"name":"{{query}}"}}` | فقط برای POST؛ فرمت JSON یا فرم در فیلد بعدی تعیین می‌شود. |
| فرمت بدنه | `JSON` / `فرم` | JSON برای دادهٔ تو در تو؛ فرم برای فیلدهای سادهٔ `application/x-www-form-urlencoded`. |
| زمان انتظار | `5` | عدد ۱ تا ۸، از جمله مقدار اعشاری مانند `2.5`. |
| اجازهٔ HTTP | خاموش | فقط برای مقصد `http://` در شبکهٔ مورد اعتماد؛ ارتباط رمزگذاری نمی‌شود. |
`{{query}}` در مقدارهای تو در توی پارامترها یا بدنه نیز جایگزین می‌شود. برای مثال `"%{{query}}%"` زمانی مفید است که خود CRM الگوی دارای `%` بخواهد. کلیدهای JSON جایگزین نمی‌شوند. Phone از امضای پویا، SOAP/XML، OAuth با refresh خودکار، GraphQL اختصاصی یا صفحه‌بندی خودکار پشتیبانی نمی‌کند؛ برای چنین قراردادهایی یک API واسط JSON بسازید.
## نگاشت پاسخ JSON
مسیر از نام کلیدهای JSON و نقطه تشکیل می‌شود؛ `[]` روی یک کلید، عضوهای آرایهٔ آن کلید را باز می‌کند. نام کلید در مسیر باید با حرف لاتین یا `_` شروع شود و سپس فقط حرف، رقم یا `_` داشته باشد. نحوهایی مانند `$.data`، `contacts[0]`، شرط‌های JSONPath و کلیدهای دارای خط تیره پشتیبانی نمی‌شوند.
| پاسخ CRM | مسیر فهرست | مسیر نام | نگاشت شماره‌ها |
| --- | --- | --- | --- |
| `[{"name":"علی","mobile":"0912..."}]` | خالی | `name` | `[{"path":"mobile","label":"همراه"}]` |
| `{"data":{"contacts":[{"full_name":"علی","mobile":"0912..."}]}}` | `data.contacts` | `full_name` | `[{"path":"mobile","label":"همراه"}]` |
| `{"data":{"contacts":[{"name":"علی","phones":[{"type":"همراه","number":"0912..."}]}]}}` | `data.contacts` | `name` | `[{"path":"phones[]","value_path":"number","label_path":"type"}]` |
| `{"results":[{"person":{"name":"علی"},"mobile":"0912...","office":"021..."}]}` | `results` | `person.name` | `[{"path":"mobile","label":"همراه"},{"path":"office","label":"دفتر"}]` |
«مسیر فهرست» نسبت به ریشهٔ پاسخ است. «مسیر نام» و هر `path` شماره نسبت به **هر عضو فهرست** هستند. `value_path` و `label_path` نسبت به مقدار یافته‌شده در همان `path` هستند. مثلاً در `phones[]` هر عضو یک شیء است؛ `value_path=number` شماره و `label_path=type` برچسب را می‌خواند. اگر `label_path` مقدار نداشته باشد، `label` ثابت استفاده می‌شود و در نبود آن برچسب «شماره» است. باید ۱ تا ۸ نگاشت شماره تعریف کنید.
نمونهٔ کامل دیگر:
```json
{
"data": {
"people": [
{
"profile": {"display_name": "مریم احمدی"},
"phones": [
{"kind": "همراه", "value": "+989121234567"},
{"kind": "داخلی", "value": "102"}
]
}
]
}
}
```
تنظیم متناظر: مسیر فهرست `data.people`، مسیر نام `profile.display_name` و نگاشت `[{"path":"phones[]","value_path":"value","label_path":"kind"}]`.
## نمونهٔ POST
اگر CRM جست‌وجو را به صورت JSON می‌پذیرد، روش را `POST`، پارامترهای URL را `{}`، فرمت را `JSON` و بدنه را به شکل زیر بگذارید:
```json
{"filter":{"name":"{{query}}"},"page_size":50}
```
این تنظیم برای جست‌وجوی «علی» بدنهٔ `{"filter":{"name":"علی"},"page_size":50}` می‌فرستد. اگر CRM فرم ساده می‌خواهد، فرمت `فرم` و بدنه را مثلاً `{"term":"{{query}}","limit":"50"}` بگذارید. برای بدنهٔ تو در تو از JSON استفاده کنید؛ نحوهٔ تفسیر فرم تو در تو به CRM بستگی دارد.
## اعتبارنامه و هدرها
رمز API را در `.env` پروژه یا محیط process اجرای Phone با پیشوند `PHONE_EXTERNAL_` قرار دهید:
```dotenv
PHONE_EXTERNAL_CRM_TOKEN=replace-with-a-long-random-token
```
سپس در فیلد هدرها یکی از الگوهای زیر را وارد کنید:
```json
{"Authorization":"Bearer {{env:PHONE_EXTERNAL_CRM_TOKEN}}","Accept":"application/json"}
```
```json
{"X-API-Key":"{{env:PHONE_EXTERNAL_CRM_TOKEN}}","Accept":"application/json"}
```
برای `Authorization`، `X-API-Key`، `Cookie` و `Proxy-Authorization` مقدار خام رمز پذیرفته نمی‌شود؛ ارجاع کامل به متغیر `PHONE_EXTERNAL_` لازم است. در `Authorization` پیشوند `Bearer ` یا `Basic ` نیز مجاز است. Phone ارجاع را هنگام هر درخواست، ابتدا از محیط process و سپس از `.env` کنار پروژه می‌خواند. تغییر متغیر محیط process نیازمند راه‌اندازی دوبارهٔ سرویس است؛ تغییر فایل `.env` برای درخواست بعدی خوانده می‌شود. توکن واقعی را در URL، نام منبع، تصویر پنل یا مستندات قرار ندهید.
نمونهٔ بررسی از میزبان Phone، با توکنی که از قبل در محیط همان shell موجود است:
```bash
curl --fail-with-body --get 'https://crm.example.com/api/phone-contacts' \
--data-urlencode 'search=علی' \
--data-urlencode 'limit=50' \
-H "Authorization: Bearer ${PHONE_EXTERNAL_CRM_TOKEN}" \
-H 'Accept: application/json'
```
اگر توکن در shell موجود نیست، آن را از مسیر امن سازمانی دریافت کنید. همچنین از میزبان Phone دسترسی DNS، TLS و شبکه به CRM را بررسی کنید. Phone تغییر مسیر HTTP را دنبال نمی‌کند و proxyهای محیطی را برای این درخواست‌ها به کار نمی‌گیرد.
## دسترسی کاربران و جریان تماس
1. کاربر وارد `/phone` می‌شود و در «مخاطبان» دست‌کم دو نویسه می‌نویسد. درخواست جست‌وجو حداکثر ۸۰ نویسه می‌پذیرد؛ Phone فاصلهٔ دو سر متن را حذف می‌کند و آن را به **همهٔ منابع فعال** می‌فرستد.
2. هر نتیجه نام، نام منبع و تا پنج گزینهٔ شماره دارد. اگر «مشاهدهٔ شمارهٔ مخاطبان» برای کاربر خاموش باشد، مقدار شماره در پاسخ Phone فرستاده نمی‌شود؛ گزینه‌هایی مثل «شمارهٔ ۱» همچنان قابل تماس‌اند.
3. Phone برای هر نتیجه شناسهٔ تصادفی موقت و وابسته به همان کاربر می‌سازد. این شناسه ۱۰ دقیقه معتبر است و شماره را در خود ندارد. هنگام تماس، مجوز «اجازهٔ تماس» و داشتن همراه یا داخلی تخصیص‌یافتهٔ کاربر بررسی می‌شود.
4. تماس از منطق click2call موجود انجام می‌شود. ویرایش، غیرفعال‌سازی یا حذف منبع، شناسه‌های تماس قبلی آن را باطل می‌کند؛ کاربر باید دوباره جست‌وجو کند.
**مرز دسترسی CRM:** درخواست بیرونی با اعتبارنامهٔ خود منبع انجام می‌شود؛ در این نسخه شناسه، نقش یا توکن کاربر Phone به CRM فرستاده نمی‌شود. بنابراین هر نامی که با آن اعتبارنامه قابل جست‌وجو باشد، برای همهٔ کاربران واردشدهٔ Phone قابل مشاهده است. مجوز «مشاهدهٔ شمارهٔ مخاطبان» فقط شماره را در پاسخ Phone پنهان می‌کند؛ جلوی ارسال متن دلخواه جست‌وجو یا تطبیق احتمالی شماره توسط CRM را نمی‌گیرد. اگر CRM مخاطبان خصوصی یا سیاست‌های جداگانه برای کاربران دارد، endpoint یا اعتبارنامهٔ محدود مخصوص Phone بسازید و خروجی آن را در CRM محدود کنید.
## محدودیت‌ها و عیب‌یابی
| نشانه | بررسی |
| --- | --- |
| منبع ذخیره نمی‌شود و خطای `422` می‌آید | JSON هر فیلد معتبر باشد؛ `{{query}}` در پارامتر یا بدنه باشد؛ مسیر نام و نگاشت شماره تعریف شوند؛ برای GET بدنه `{}` باشد؛ آدرس HTTPS یا گزینهٔ اجازهٔ HTTP فعال باشد. |
| «موردی پیدا نشد» با وجود نتیجه در CRM | نمونهٔ پاسخ واقعی را ببینید؛ مسیر فهرست، نام و شماره را نسبت به محل درست تنظیم کنید. نام خالی یا همهٔ شماره‌های نامعتبر باعث حذف نتیجه می‌شوند. |
| «برخی منابع پاسخ ندادند» | اتصال DNS/TLS، کد HTTP، اعتبارنامه، زمان انتظار، JSON معتبر و سقف ۱ MiB را از سرور Phone بررسی کنید. خطای یک منبع، نتیجهٔ منابع سالم را حذف نمی‌کند. |
| پس از ویرایش منبع تماس قبلی برقرار نمی‌شود | شناسه‌های موقت با هر ویرایش باطل می‌شوند؛ دوباره جست‌وجو کنید. |
| شماره برای یک کاربر دیده نمی‌شود | مجوز «مشاهدهٔ شمارهٔ مخاطبان» همان کاربر را بررسی کنید. پنهان‌بودن شماره مانع تماس از دکمهٔ نتیجه نیست. |
| دکمهٔ تماس کار نمی‌کند | مجوز «اجازهٔ تماس»، همراه/داخلی تخصیص‌یافته، فعال‌بودن منبع و اعتبار ۱۰ دقیقه‌ای شناسه را بررسی کنید. |
حداکثر ۲۰ منبع تعریف می‌شود و حداکثر چهار درخواست بیرونی هم‌زمان اجرا می‌شود. دادهٔ پاسخ در دفترچهٔ محلی ذخیره نمی‌شود؛ فقط شماره‌های لازم برای شناسه‌های تماس موقت در SQLite نگهداری می‌شوند. شناسه پس از ۱۰ دقیقه قابل استفاده نیست و ردیف منقضی‌شده هنگام ایجاد شناسه‌های بعدی پاک می‌شود.