# دليل تكامل الدعم داخل أنظمة عراق سوفت

> **الهدف:** أن يجد كل عميل زر «الدعم الفني» داخل حسابه في نظامنا مباشرة —
> يضغط عليه فتُفتح محادثة الدعم الكاملة (نص، مرفقات، رسائل صوتية، مكالمة، فيديو، مشاركة شاشة)
> **دون مغادرة الصفحة، ودون تسجيل دخول، ودون أن نرسل له رابطًا يدويًا.**

**الجمهور:** فرق تطوير أنظمة عراق سوفت (سبيدو نكست، منيو فودز، نظام المدارس، نظام السفر، …).
**المنصة:** `https://support.md-soft.app`
**آخر تحديث:** 2026-08-25 · النسخة 2.0

---

## المحتويات

1. [الفكرة في سطرين](#1-الفكرة-في-سطرين)
2. [تسلسل المفاتيح](#2-تسلسل-المفاتيح)
3. [كيف يعمل التكامل](#3-كيف-يعمل-التكامل)
4. [الخطوة 1: احصل على مفتاح برنامجك](#4-الخطوة-1-احصل-على-مفتاح-برنامجك)
5. [الخطوة 2: خزّن مفتاح كل عميل](#5-الخطوة-2-خزّن-مفتاح-كل-عميل)
6. [الخطوة 3: اطلب تذكرة من خادمك](#6-الخطوة-3-اطلب-تذكرة-من-خادمك)
7. [الخطوة 4: شغّل الودجت](#7-الخطوة-4-شغّل-الودجت)
8. [أمثلة كاملة بكل اللغات](#8-أمثلة-كاملة-بكل-اللغات)
9. [خيارات الودجت الكاملة](#9-خيارات-الودجت-الكاملة)
10. [واجهة JavaScript والأحداث](#10-واجهة-javascript-والأحداث)
11. [تضمين المتابعة](#11-تضمين-المتابعة)
12. [بدائل الودجت](#12-بدائل-الودجت)
13. [ماذا يرى موظف الدعم](#13-ماذا-يرى-موظف-الدعم)
14. [الأمان — قواعد غير قابلة للتفاوض](#14-الأمان--قواعد-غير-قابلة-للتفاوض)
15. [إدارة المفاتيح وتدويرها](#15-إدارة-المفاتيح-وتدويرها)
16. [مرجع الـ API](#16-مرجع-الـ-api)
17. [حل المشاكل](#17-حل-المشاكل)
18. [قائمة التحقق قبل الإطلاق](#18-قائمة-التحقق-قبل-الإطلاق)
19. [ملحق: المسار المتقدّم بتوقيع HMAC](#19-ملحق-المسار-المتقدّم-بتوقيع-hmac)

---

## 1) الفكرة في سطرين

**مفتاحان يفتحان الدعم:**

```
مفتاح البرنامج (pk_…)  →  أي برنامج يتكلم؟    ← واحد لكل نظام، في متغيرات بيئة خادمك
مفتاح العميل   (ck_…)  →  أي عميل يطلب الدعم؟ ← واحد لكل عميل، في سجل ذلك العميل لديك
```

ترسلهما من خادمك، فتحصل على **تذكرة دخول** قصيرة العمر تفتحها في الودجت.
لا توقيع، لا طوابع زمنية، لا تشفير — مفتاحان ونداء واحد.

---

## 2) تسلسل المفاتيح

```
منصة الدعم                                    نظامك (سبيدو نكست مثلًا)
──────────────                                ────────────────────────

المنتج: سبيدو نكست                              .env
  └─ integration_key = pk_Lg8R…      ─────────►  ISUPPORT_PRODUCT_KEY=pk_Lg8R…
                                                 (مرة واحدة لكل النظام)

  العميل: كافيه لافيتا
    └─ integration_key = ck_axc6…    ─────────►  جدول العملاء لديك
  العميل: مطعم الكبابجي                             ├─ كافيه لافيتا → ck_axc6…
    └─ integration_key = ck_jmXe…    ─────────►     └─ الكبابجي    → ck_jmXe…
                                                 (عمود واحد مع كل عميل)
```

| | مفتاح البرنامج | مفتاح العميل |
|---|---|---|
| البادئة | `pk_` | `ck_` |
| يُنشأ عند | إضافة منتج في منصة الدعم | إضافة عميل في منصة الدعم |
| العدد | واحد لكل نظام | واحد لكل عميل |
| يُخزَّن في | متغيرات بيئة خادم النظام | عمود في جدول عملاء النظام |
| يراه | مدير النظام فقط | مدير النظام فقط |
| يُدوَّر من | لوحة الإدارة ← التكامل والتضمين | لوحة الإدارة ← العميل ← الروابط |

> 🔒 **كلاهما سرّي.** يُرسلان من خادم إلى خادم فقط. المتصفح لا يرى إلا التذكرة المؤقتة.

**حماية مدمجة:** مفتاح برنامج لا يفتح عميل برنامج آخر — لو تسرّب مفتاح سبيدو نكست
فلن يفتح عملاء منيو فودز، لأن المنصة تتحقق أن العميل تابع لذلك البرنامج.

---

## 3) كيف يعمل التكامل

```
┌──────────────────────────────────────────────────────────────────────┐
│  نظامك — متصفح العميل                                                 │
│                                                                       │
│   صفحة حساب المطعم                          ┌──────────────────────┐ │
│   ┌───────────────────────────────┐         │  iframe (معزول)      │ │
│   │  ... واجهة نظامك كما هي ...   │         │  محادثة الدعم        │ │
│   └───────────────────────────────┘         │  support.md-soft.app │ │
│                                              └──────────────────────┘ │
│   [🗨 الدعم الفني]  ← زر عائم من embed.js                             │
└──────────────────────────────────────────────────────────────────────┘
             ▲                                          ▲
             │ سطر <script> + التذكرة                    │ iframe يفتح /s/<تذكرة>?embed=1
             │                                          │
┌────────────┴──────────────┐                ┌──────────┴─────────────────┐
│  خادم نظامك               │  المفتاحان     │  منصة الدعم                │
│  pk_… + ck_…              ├───────────────►│  POST /api/embed/session   │
│                           │◄───────────────┤  ترجع: ticket + url        │
└───────────────────────────┘                └────────────────────────────┘
```

**ما لا تحتاج لبنائه:** لا واجهة محادثة، لا مكالمات، لا رفع ملفات، لا إشعارات — كل ذلك جاهز داخل الإطار.

---

## 4) الخطوة 1: احصل على مفتاح برنامجك

يقوم بها **مدير منصة الدعم** مرة واحدة:

**من أي من المكانين:**

| المكان | ماذا تجد |
|---|---|
| لوحة الإدارة ← **المنتجات** | عمود «مفتاح التكامل» مع زر **«إظهار المفتاح»** لكل برنامج |
| لوحة الإدارة ← **التكامل والتضمين** | نفس المفتاح + النطاقات وعمر التذكرة والتدوير والإيقاف |

المفتاح يظهر مقنّعًا في الجدول (`pk_Lg8RhGea…`) — اضغط «إظهار المفتاح» ليظهر كاملًا مع زر نسخ.

ثم اضغط **«النطاقات والإعدادات»** وسجّل نطاقات نظامك:

```
https://speedo-next.md-soft.app
https://*.menu-foods.com
```

| القاعدة | مثال |
|---|---|
| النطاق الكامل يطابق نفسه فقط | `https://speedo-next.md-soft.app` |
| `*.` يطابق كل النطاقات الفرعية | `https://*.menu-foods.com` ← `kababji.menu-foods.com` ✓ · `a.b.menu-foods.com` ✓ |
| ولا يطابق النطاق الأساسي | `menu-foods.com` ✗ |
| ولا المتشابه | `evil-menu-foods.com` ✗ |
| البروتوكول جزء من المطابقة | نمط `https://` لا يقبل `http://` |

> ❗ **بدون تسجيل النطاق سيمنع المتصفح الإطار** ولن تظهر المحادثة (خطأ `frame-ancestors` في Console).
> سجّل نطاقات التطوير أيضًا إن كنت تجرّب محليًا (`http://localhost:3000`).

**البرنامج غير موجود؟** أضفه من: لوحة الإدارة ← **المنتجات** ← «منتج جديد» — يأخذ مفتاحًا تلقائيًا عند الإنشاء.

ضع المفتاح في بيئة خادمك:

```bash
ISUPPORT_BASE=https://support.md-soft.app
ISUPPORT_PRODUCT_KEY=pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

---

## 5) الخطوة 2: خزّن مفتاح كل عميل

كل عميل في منصة الدعم له مفتاحه الخاص. من: لوحة الإدارة ← **العملاء والروابط** ← افتح العميل
← تبويب **الروابط** ← **مفتاح تكامل هذا العميل** ← انسخه.

أضف عمودًا في جدول عملائك:

```sql
ALTER TABLE customers ADD COLUMN support_key VARCHAR(80) NULL;
```

| عميلك | `support_key` |
|---|---|
| كافيه لافيتا | `ck_axc6PZbHAf9…` |
| مطعم الكبابجي | `ck_jmXelwkKTZH…` |

> **العميل غير موجود في منصة الدعم؟** أضفه من لوحة الإدارة — سيأخذ مفتاحًا تلقائيًا عند الإنشاء.
> اجعل **المنتج** الخاص به هو برنامجك، وإلا رفضت المنصة فتح دعمه بمفتاح برنامجك.

---

## 6) الخطوة 3: اطلب تذكرة من خادمك

**الطلب** — `POST https://support.md-soft.app/api/embed/session`

```json
{
  "product_key": "pk_Lg8R…",
  "customer_key": "ck_axc6…",

  "contact": { "ref": "u-55", "name": "علي حسين", "phone": "07701112233" },
  "branch":  { "ref": "br-2", "name": "فرع الجادرية" },
  "meta":    { "app_version": "3.4.1", "terminal_id": "POS-02", "active_cashier": "علي" }
}
```

| الحقل | إلزامي | الشرح |
|---|---|---|
| `product_key` | ✅ | من متغيرات بيئة خادمك |
| `customer_key` | ✅ | من سجل العميل في نظامك |
| `contact.ref` | — | معرّف المستخدم في نظامك |
| `contact.name` / `phone` | — | يظهران لموظف الدعم بدل سؤال العميل |
| `branch.ref` / `name` | — | الفرع؛ يُنشأ تلقائيًا إن لم يكن موجودًا ويُربط بـ `ref` |
| `meta` | — | أي بيانات تقنية تساعد الدعم (تظهر في تبويب «تقني») |
| `origin` | — | نطاق الصفحة؛ يُقرأ تلقائيًا من ترويسة `Origin` إن لم تُرسله |

**الرد** — `201 Created`

```json
{
  "ok": true,
  "ticket": "et_8kQm…",
  "url": "https://support.md-soft.app/s/et_8kQm…?embed=1",
  "expires_at": "2026-08-25T18:30:00.000Z",
  "product":  { "id": 1, "code": "SPEEDO", "name": "Speedo Next" },
  "customer": { "id": 12, "code": "C-4821", "name": "كافيه لافيتا" },
  "branch":   { "id": 5, "name": "فرع الجادرية" },
  "unread": 2,
  "followup": {
    "code": "FU-20260825-00001",
    "title": "متابعة تشغيل سبيدو نكست",
    "ends_at": "2027-08-25T00:00:00.000Z",
    "ticket": "et_2pLw…",
    "url": "https://support.md-soft.app/f/et_2pLw…?embed=1"
  }
}
```

`followup` يظهر فقط إن كانت للعميل متابعة جارية — استعمله لعرض زر ثانٍ (انظر §11).

**عمر التذكرة** 12 ساعة افتراضيًا (يُضبط لكل برنامج). أنشئها عند تحميل الصفحة أو عند أول ضغط على الزر.

---

## 7) الخطوة 4: شغّل الودجت

```html
<script src="https://support.md-soft.app/embed.js" defer></script>
<script>
  window.addEventListener('iraqsoft-support:ready', async function () {
    const res = await fetch('/api/support/ticket', { credentials: 'same-origin' });
    const data = await res.json();
    if (!data.ticket) return;                       // لا تُعطّل الصفحة إن فشل الدعم
    IraqSoftSupport.init({
      ticket: data.ticket,
      label: 'الدعم الفني',
      position: 'bottom-left',
      color: '#0a2d4c'
    });
  });
</script>
```

`/api/support/ticket` مسار **في نظامك أنت** — هو الذي يحمل المفتاحين ويستدعي منصة الدعم.

---

## 8) أمثلة كاملة بكل اللغات

كلها تنفّذ الشيء نفسه: نداء `POST` واحد بمفتاحين. **لا تشفير ولا توقيع.**

### Node.js / Express

```js
const BASE = process.env.ISUPPORT_BASE;
const PRODUCT_KEY = process.env.ISUPPORT_PRODUCT_KEY;

async function supportTicket({ customerKey, contact = {}, branch = null, meta = {} }) {
  const res = await fetch(`${BASE}/api/embed/session`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ product_key: PRODUCT_KEY, customer_key: customerKey, contact, branch, meta }),
  });
  if (!res.ok) throw new Error(`support ${res.status}: ${await res.text()}`);
  return res.json();
}

app.get('/api/support/ticket', async (req, res) => {
  try {
    const data = await supportTicket({
      customerKey: req.user.customer.supportKey,     // من سجل العميل، لا من المتصفح
      contact: { ref: String(req.user.id), name: req.user.name, phone: req.user.phone },
      branch: { ref: String(req.user.branchId), name: req.user.branchName },
      meta: { app_version: APP_VERSION, terminal_id: req.user.terminalId },
    });
    res.json({ ticket: data.ticket, unread: data.unread, followup: data.followup });
  } catch (err) {
    console.error('support ticket failed', err.message);
    res.json({ ticket: null });                      // الصفحة تعمل حتى لو تعذّر الدعم
  }
});
```

### PHP / Laravel

```php
function isupport_ticket(string $customerKey, array $contact = [], ?array $branch = null, array $meta = []): array
{
    $body = [
        'product_key'  => env('ISUPPORT_PRODUCT_KEY'),
        'customer_key' => $customerKey,
        'contact'      => (object) $contact,
        'branch'       => $branch,
        'meta'         => (object) $meta,
    ];

    $ch = curl_init(env('ISUPPORT_BASE') . '/api/embed/session');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
        CURLOPT_POSTFIELDS     => json_encode($body, JSON_UNESCAPED_UNICODE),
        CURLOPT_TIMEOUT        => 10,
    ]);
    $response = curl_exec($ch);
    $status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($status !== 201) throw new RuntimeException("support: $status $response");
    return json_decode($response, true);
}
```

```php
// Controller
public function ticket(Request $request)
{
    $user = $request->user();
    try {
        $data = isupport_ticket(
            $user->customer->support_key,
            ['ref' => (string) $user->id, 'name' => $user->name, 'phone' => $user->phone],
            ['ref' => (string) $user->branch_id, 'name' => $user->branch->name],
            ['app_version' => config('app.version'), 'terminal_id' => $user->terminal_id]
        );
        return response()->json(['ticket' => $data['ticket'], 'followup' => $data['followup'] ?? null]);
    } catch (\Throwable $e) {
        report($e);
        return response()->json(['ticket' => null]);
    }
}
```

### C# / .NET

```csharp
public sealed class SupportClient(HttpClient http, IConfiguration config)
{
    public async Task<JsonElement> TicketAsync(string customerKey, object? contact = null,
                                               object? branch = null, object? meta = null)
    {
        var body = new
        {
            product_key  = config["ISUPPORT_PRODUCT_KEY"],
            customer_key = customerKey,
            contact,
            branch,
            meta
        };
        var res = await http.PostAsJsonAsync($"{config["ISUPPORT_BASE"]}/api/embed/session", body);
        res.EnsureSuccessStatusCode();
        return await res.Content.ReadFromJsonAsync<JsonElement>();
    }
}
```

### Python / Django · FastAPI

```python
import os, requests

BASE = os.environ["ISUPPORT_BASE"]
PRODUCT_KEY = os.environ["ISUPPORT_PRODUCT_KEY"]

def support_ticket(customer_key, contact=None, branch=None, meta=None):
    res = requests.post(f"{BASE}/api/embed/session", timeout=10, json={
        "product_key": PRODUCT_KEY,
        "customer_key": customer_key,
        "contact": contact or {},
        "branch": branch,
        "meta": meta or {},
    })
    res.raise_for_status()
    return res.json()
```

---

## 9) خيارات الودجت الكاملة

كل خيار يُضبط إما بسمة `data-*` على وسم `<script>` أو بمفتاح في `IraqSoftSupport.init({...})`.

| `data-*` | `init()` | الافتراضي | الشرح |
|---|---|---|---|
| `data-ticket` | `ticket` | — | التذكرة من خادمك (الوضع الأساسي) |
| `data-token` | `token` | — | توكن رابط دائم — للتجربة السريعة فقط |
| `data-url` | `url` | — | عنوان كامل جاهز (يتجاوز الاثنين أعلاه) |
| `data-kind` | `kind` | `support` | `support` أو `followup` |
| `data-position` | `position` | `bottom-left` | `bottom-left` أو `bottom-right` |
| `data-color` | `color` | `#0a2d4c` | لون الزر والشريط |
| `data-label` | `label` | `الدعم الفني` | نص الزر — اتركه فارغًا لزر أيقونة فقط |
| `data-offset` | `offset` | `24` | بُعد الزر عن الحافة (بكسل) |
| `data-z-index` | `zIndex` | `2147483000` | ارفعه إن كان لديك عناصر فوق الزر |
| `data-auto-open` | `autoOpen` | `false` | فتح اللوحة تلقائيًا عند التحميل |
| `data-hide-launcher` | `hideLauncher` | `false` | إخفاء الزر — لتفتحه من زرّك أنت |

```html
<!-- بلا زر عائم: نفتحه من عنصر في قائمة نظامك -->
<script src="https://support.md-soft.app/embed.js" data-hide-launcher="true" defer></script>
<script>
  document.querySelector('#menu-support').addEventListener('click', function (e) {
    e.preventDefault();
    IraqSoftSupport.open();
  });
</script>
```

---

## 10) واجهة JavaScript والأحداث

### الدوال

```js
IraqSoftSupport.init(options)      // تركيب الودجت
IraqSoftSupport.open() / close() / toggle()
IraqSoftSupport.identify({ticket}) // تبديل المستخدم/العميل دون إعادة تحميل الصفحة
IraqSoftSupport.show(kind, ticket) // فتح صفحة الدعم أو المتابعة مباشرة
IraqSoftSupport.on(event, handler) / off(event, handler)
IraqSoftSupport.destroy()
IraqSoftSupport.unread             // عدد غير المقروء (قراءة فقط)
```

### الأحداث

| الحدث | متى | الحمولة |
|---|---|---|
| `mounted` | رُكّب الودجت | `{}` |
| `ready` | حمّل الإطار وجاهز | `{ version, href }` |
| `open` / `close` | فُتحت/أُغلقت اللوحة | `{}` |
| `unread` | تغيّر عدد غير المقروء | `{ count }` |
| `session` | أنشأ العميل طلب دعم | `{ id, ticket_no, status }` |
| `call` | تغيّرت حالة مكالمة | `{ kind, status }` |

```js
// شارة داخل قائمة نظامك تعكس غير المقروء
IraqSoftSupport.on('unread', function (e) {
  document.querySelector('#navSupportBadge').textContent = e.count || '';
});
```

كل حدث يُطلَق أيضًا على `window` باسم `iraqsoft-support:<event>`.

---

## 11) تضمين المتابعة

**المتابعة** التزام طويل الأمد مع عميل لمدة محددة، وله **رابط منفصل تمامًا** عن الدعم (`/f/` لا `/s/`).
إن كانت للعميل متابعة جارية، رد `/api/embed/session` يتضمن كائن `followup` بتذكرته الخاصة.

```html
<script src="https://support.md-soft.app/embed.js" defer></script>
<script>
  window.addEventListener('iraqsoft-support:ready', function () {
    IraqSoftSupport.init({ ticket: DATA.supportTicket, label: 'الدعم الفني' });

    if (DATA.followup) {
      var btn = document.querySelector('#menuFollowup');
      btn.hidden = false;
      btn.textContent = 'المتابعة — ' + DATA.followup.title;
      btn.addEventListener('click', function () {
        IraqSoftSupport.show('followup', DATA.followup.ticket);
      });
    }
  });
</script>
```

---

## 12) بدائل الودجت

### (أ) `iframe` بيدك

```html
<iframe src="https://support.md-soft.app/s/{{ $ticket }}?embed=1"
        style="width:100%;height:640px;border:0;border-radius:12px"
        allow="camera; microphone; display-capture; autoplay; clipboard-write; fullscreen"
        title="الدعم الفني"></iframe>
```

> `allow` **ضروري** — بدونه لن تعمل المكالمات ولا الكاميرا ولا مشاركة الشاشة.

### (ب) نافذة/تبويب

```js
window.open(ticketUrl.replace('?embed=1', ''), '_blank', 'noopener');
```

### (ج) تطبيق سطح مكتب (Electron / WebView2)

استخدم `iframe` كما في (أ) داخل نافذتك. تأكد من منح صلاحيات الكاميرا والميكروفون في إعدادات التطبيق.

---

## 13) ماذا يرى موظف الدعم

| في لوحة الدعم | مصدرها |
|---|---|
| اسم المطعم والفرع | `customer_key` + `branch` |
| اسم المتصل ورقمه | `contact.name` / `contact.phone` |
| وسم «قادم من التضمين» | تلقائي (`origin = embed`) |
| بادئة رقم التذكرة | من المنتج (`SP-` · `MF-` …) |
| تبويب **تقني**: نسخة التطبيق، رقم الطرفية، الكاشير النشط… | `meta` |
| سجل العميل السابق وتقييماته | تلقائي من المنصة |

**النتيجة العملية:** لا يسأل الموظف «مين حضرتك؟ من أي فرع؟ شنو نسخة البرنامج؟» — كلها أمامه.
اجعل `meta` غنية قدر الإمكان؛ كل حقل فيها يوفّر دقيقة من وقت المكالمة.

```json
{
  "app_version": "3.4.1",
  "terminal_id": "POS-02",
  "active_cashier": "علي حسين",
  "printer_status": "متصلة",
  "last_sync": "2026-08-25T09:12:00Z",
  "open_shift": "صباحي",
  "db_size_mb": 412
}
```

---

## 14) الأمان — قواعد غير قابلة للتفاوض

| # | القاعدة | السبب |
|---|---|---|
| 1 | **المفتاحان لا يظهران في المتصفح إطلاقًا** — لا في JS، لا في HTML، لا في مستودع الكود | من يملكهما يفتح دعم ذلك العميل |
| 2 | `customer_key` يُقرأ من **سجل العميل على خادمك**، لا من طلب المتصفح | وإلا فتح أي مستخدم دعم أي عميل |
| 3 | سجّل **النطاقات المسموح لها** بدقة — `*.` للنطاقات الفرعية فقط، لا كنطاق مفتوح | يمنع تأطير الدعم من مواقع غريبة |
| 4 | اجعل عمر التذكرة قصيرًا (12 ساعة أو أقل) | يقلّل أثر أي تسريب |
| 5 | استخدم HTTPS في نظامك | الكاميرا والميكروفون لا تعملان على HTTP |
| 6 | لا تُسجّل المفاتيح في ملفات اللوج | تسريب صامت |

**ما تفعله المنصة تلقائيًا:**

- مفتاح برنامج **لا يفتح** عميل برنامج آخر.
- عميل موقوف أو برنامج موقوف ⇒ التذاكر تتوقف **فورًا**، حتى الصادرة قبل الإيقاف.
- التذاكر تنتهي صلاحيتها تلقائيًا.
- التأطير مسموح فقط من النطاقات المسجّلة، ولوحات الموظفين ممنوعة من التأطير دائمًا.
- مفتاح العميل يظهر كاملًا لمدير النظام فقط، ومقنّعًا لبقية الموظفين.

---

## 15) إدارة المفاتيح وتدويرها

| الحالة | الإجراء | الأثر |
|---|---|---|
| تسرّب مفتاح برنامج | لوحة الإدارة ← التكامل ← **تدوير المفتاح** | يتوقف الدعم في كل نسخ البرنامج حتى تُحدَّث بيئة الخادم |
| تسرّب مفتاح عميل | لوحة الإدارة ← العميل ← الروابط ← **تدوير مفتاح العميل** | يتوقف دعم ذلك العميل وحده حتى يُحدَّث سجله لديك |
| إيقاف تكامل برنامج مؤقتًا | **إيقاف التكامل** على بطاقة البرنامج | كل تذاكره تتوقف فورًا ولا تُصدر جديدة |
| إنهاء تعاقد عميل | تعطيل العميل في منصة الدعم | تذاكره تتوقف تلقائيًا |

> **خطة التدوير الآمنة:** التدوير فوري — حضّر متغير البيئة الجديد، اضغط «تدوير»، ثم أعِد تشغيل خدمتك مباشرة.
> جرّبه في بيئة التطوير أولًا.

---

## 16) مرجع الـ API

### `POST /api/embed/session`
إصدار تذكرة دخول. **من خادمك فقط.**

| الرمز | المعنى | ماذا تفعل |
|---|---|---|
| `201` | نجح | استخدم `ticket` / `url` |
| `400` | حقل ناقص | `product_key` و`customer_key` كلاهما مطلوب |
| `403` | مفتاح خاطئ · برنامج/عميل موقوف · العميل لا يتبع البرنامج · نطاق ممنوع | اقرأ رسالة `error` |
| `429` | تجاوز 300 طلب/دقيقة | خزّن التذكرة مؤقتًا بدل طلبها كل مرة |

### إدارة المفاتيح (**مدير النظام** بجلسة لوحة الإدارة)

| الطريقة | المسار | الوصف |
|---|---|---|
| `GET` | `/api/embed/products` | كل البرامج بإعدادات تكاملها (المفاتيح مقنّعة) |
| `GET` | `/api/embed/products/:id/key` | كشف مفتاح برنامج كاملًا |
| `PATCH` | `/api/embed/products/:id` | `{allowed_origins?, ticket_ttl_minutes?, integration_status?}` |
| `POST` | `/api/embed/products/:id/rotate` | تدوير مفتاح البرنامج |
| `POST` | `/api/embed/customers/:id/rotate` | تدوير مفتاح عميل |

مفتاح العميل يظهر ضمن `GET /api/customers/:id` — كاملًا لمدير النظام،
ومقنّعًا (`integration_key_preview`) لبقية الموظفين.

### صفحات وملفات

| المسار | الوصف |
|---|---|
| `/embed.js` | سكربت الودجت — يُقدَّم بـ `no-cache` فيصل أي تحديث لكل الأنظمة فورًا |
| `/embed/demo` | صفحة تجربة التكامل للفريق |
| `/s/<ticket>?embed=1` | صفحة الدعم بوضع التضمين |
| `/f/<ticket>?embed=1` | صفحة المتابعة بوضع التضمين |
| `/docs/INTEGRATION.md` | هذا الدليل مقدَّمًا من الخادم |

التذكرة تعمل في **كل** مسارات `/api/portal` و`/api/followup` وفي مصادقة Socket.IO وتنزيل المرفقات —
موثّقة في [`API.md`](API.md).

---

## 17) حل المشاكل

| العَرَض | السبب الغالب | الحل |
|---|---|---|
| `403 مفتاح البرنامج غير صحيح` | المفتاح ناقص أو قديم بعد تدوير | انسخه من جديد من لوحة الإدارة |
| `403 مفتاح العميل غير صحيح` | العميل غير مسجّل أو مفتاحه دُوِّر | حدّث `support_key` في سجل العميل لديك |
| `403 هذا العميل لا يتبع هذا البرنامج` | حقل «المنتج» عند العميل يشير لبرنامج آخر | صحّحه من لوحة الإدارة ← العميل ← تعديل |
| `403 النطاق … غير مسموح` | النطاق غير مسجّل | أضفه في «النطاقات والإعدادات» للبرنامج |
| الإطار أبيض / لا يفتح | نفس السبب أعلاه | تحقق من Console: خطأ `frame-ancestors` |
| المكالمة لا تعمل داخل الإطار | نقص سمة `allow` أو موقعك على HTTP | أضف `allow="camera; microphone; display-capture; …"` وانقل الموقع إلى HTTPS |
| الطلبات تصل باسم عميل واحد | ترسل نفس `customer_key` للجميع | اقرأه من سجل كل عميل |
| اسم المتصل لا يظهر للموظف | لم ترسل `contact` | أرسل `contact.name` مع الطلب |
| الزر خلف عناصر نظامك | تعارض `z-index` | ارفع `data-z-index` |
| التذكرة تنتهي أثناء عمل العميل | عمر التذكرة قصير | ارفعه من «النطاقات والإعدادات»، أو جدّدها عند فتح الودجت |

**تشخيص سريع:**

```bash
# هل المفتاحان صحيحان؟ (بدّل القيم)
curl -s -X POST https://support.md-soft.app/api/embed/session \
  -H 'Content-Type: application/json' \
  -d '{"product_key":"pk_…","customer_key":"ck_…"}' | head -c 400

# هل النطاق مسموح؟ (ابحث عن frame-ancestors)
curl -sI https://support.md-soft.app/s/<ticket> | grep -i content-security-policy
```

---

## 18) قائمة التحقق قبل الإطلاق

**منصة الدعم**
- [ ] البرنامج موجود في «المنتجات» وله مفتاح
- [ ] كل نطاقات الإنتاج مسجّلة في «النطاقات والإعدادات»
- [ ] كل العملاء مسجّلون، وحقل «المنتج» عند كل واحد يشير لبرنامجك الصحيح
- [ ] عمر التذكرة مناسب لطول جلسة العمل

**خادم نظامك**
- [ ] `ISUPPORT_PRODUCT_KEY` في متغيرات البيئة (وليس في الكود)
- [ ] عمود `support_key` مضاف لجدول العملاء ومملوء
- [ ] `customer_key` يُقرأ من سجل العميل على الخادم
- [ ] المفاتيح غير موجودة في المستودع ولا في اللوج
- [ ] فشل نداء الدعم لا يُعطّل الصفحة

**الواجهة**
- [ ] الودجت يظهر في كل صفحات حساب العميل
- [ ] الموقع يعمل على HTTPS
- [ ] جُرّبت المكالمة الصوتية والفيديو ومشاركة الشاشة من داخل الإطار
- [ ] جُرّب على الهاتف (تملأ اللوحة الشاشة)
- [ ] `meta` تحمل بيانات تقنية مفيدة فعلًا
- [ ] زر المتابعة يظهر عند وجود متابعة جارية

**القبول النهائي**
- [ ] عميل تجريبي يفتح الدعم من داخل النظام ويصل الطلب للوحة الدعم باسمه وفرعه
- [ ] تبويب «تقني» في لوحة الدعم يعرض بيانات `meta`
- [ ] شارة غير المقروء تتحدث في قائمة نظامك عبر حدث `unread`

---

## 19) ملحق: المسار المتقدّم بتوقيع HMAC

مسار بديل يبقى مدعومًا: بدل مفتاح العميل، ترسل معرّف العميل في نظامك (`customer_ref`) مع توقيع
`HMAC-SHA256` وطابع زمني. يفيد إذا كنت لا تريد تخزين مفتاح لكل عميل، أو تريد **إنشاء العملاء تلقائيًا**
عند أول اتصال — وهو ما لا يوفّره مسار المفاتيح (لأن المفتاح يُنشأ مع العميل).

```
payload   = app_id + "." + customer_ref + "." + contact_ref + "." + timestamp
signature = HMAC_SHA256(secret, payload)  →  hex بحروف صغيرة
```

```json
POST /api/embed/session
{ "app_id": "app_…", "customer_ref": "REST-1042", "timestamp": 1787654321, "signature": "3f7c…", … }
```

- يُدار من: لوحة الإدارة ← التكامل والتضمين ← **تطبيقات التكامل بتوقيع HMAC** (قسم مطوي).
- نافذة زمنية ±5 دقائق — اضبط ساعة خادمك عبر NTP.
- `POST /api/embed/verify` يشخّص التوقيع: يرجع النص المتوقّع والتوقيع الصحيح ووقت الخادم.

> **لأغلب الحالات استخدم مسار المفاتيح** — أبسط، وبلا كود تشفير، وبلا اعتماد على ساعة الخادم.

---

**للاستفسار:** فريق منصة الدعم — عراق سوفت.
