# مسار التثبيت عبر OAuth

يصل التطبيق إلى المتجر عبر مسار `OAuth 2.0` برمز التفويض مع `PKCE`. يوافق التاجر على تطبيقك في شاشة موافقة تابعة لـ `DZBuild`، ثم يبادل خادمك الرمز برمز وصول واحد لكل متجر، وكل رمز يستدعي واجهة `REST` لمتجره فقط.

## نقاط الوصول[​](#نقاط-الوصول "رابط مباشر إلى نقاط الوصول")

| الخطوة            | الطلب                                                              | من يرسله                                  |
| ----------------- | ------------------------------------------------------------------ | ----------------------------------------- |
| التفويض           | `GET https://dzbuild.com/oauth/apps/authorize`                     | متصفح التاجر، يوجّهه تطبيقك               |
| الموافقة أو الرفض | `POST https://dzbuild.com/oauth/apps/approve` و `/oauth/apps/deny` | نموذج الموافقة. تطبيقك لا يستدعيهما أبدا. |
| تبادل الرمز       | `POST https://dzbuild.com/oauth/apps/token`                        | خادمك                                     |
| استدعاءات الواجهة | `https://api.dzbuild.app/v1/...`                                   | خادمك، مع رمز الوصول                      |

## خطوات المسار[​](#خطوات-المسار "رابط مباشر إلى خطوات المسار")

1. ينشئ خادمك متحقق `PKCE` وتحدّيه بطريقة `S256`، وقيمة `state` عشوائية.
2. يوجّه التاجر إلى عنوان التفويض. التاجر غير المسجّل يدخل حسابه أولا ثم يعود إلى العنوان نفسه.
3. تعرض `DZBuild` شاشة الموافقة. يختار التاجر المتاجر ويوافق.
4. تعيد `DZBuild` توجيه المتصفح إلى `redirect_uri` الخاص بك مع `code` وقيمة `state`.
5. يرسل خادمك الرمز والمتحقق وبيانات اعتماد العميل إلى نقطة التبادل.
6. يحمل الرد رمز وصول لكل متجر ثُبّت عليه التطبيق.

## الخطوة 1: توجيه التاجر إلى عنوان التفويض[​](#الخطوة-1-توجيه-التاجر-إلى-عنوان-التفويض "رابط مباشر إلى الخطوة 1: توجيه التاجر إلى عنوان التفويض")

| المعامل                 | إلزامي | القاعدة                                                                                                                                                                   |
| ----------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `response_type`         | نعم    | دائما `code`.                                                                                                                                                             |
| `client_id`             | نعم    | معرّف العميل لتطبيقك من منصة المطورين: `dzapp_` يليه 20 حرفا ست عشريا.                                                                                                    |
| `redirect_uri`          | نعم    | أحد عناوين إعادة التوجيه المسجّلة على التطبيق، حرفا بحرف. لا مطابقة ببادئة أو بنمط.                                                                                       |
| `scope`                 | لا     | صلاحيات يفصل بينها فراغ. يجب أن تكون كل صلاحية مسجّلة على التطبيق ومسموحا بها للتطبيقات. غيابه أو كونه فارغا يعني كل الصلاحيات المسجّلة على التطبيق. 512 حرفا على الأكثر. |
| `state`                 | نعم    | من 1 إلى 1024 حرفا. تعيده `DZBuild` دون تغيير. اربطه بجلسة التاجر وتحقّق منه عند العودة.                                                                                  |
| `code_challenge`        | نعم    | بصمة `SHA-256` للمتحقق بترميز `base64url` دون حشو: 43 حرفا بالضبط من `A-Z a-z 0-9 - _`.                                                                                   |
| `code_challenge_method` | نعم    | دائما `S256`. طريقة `plain` مرفوضة.                                                                                                                                       |

يتكوّن متحقق الرمز من 43 إلى 128 حرفا من `A-Z a-z 0-9 - . _ ~`. احتفظ به على خادمك حتى تبادل الرمز. لا يمرّ أبدا عبر المتصفح.

`PKCE` بلغة PHP:

```
<?php

function base64url(string $bytes): string

{

    return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');

}



$verifier = base64url(random_bytes(32));   // 43 characters

$challenge = base64url(hash('sha256', $verifier, true));



// Keep $verifier on your server (session or database) until the token call.

$authorizeUrl = 'https://dzbuild.com/oauth/apps/authorize?' . http_build_query([

    'response_type' => 'code',

    'client_id' => 'dzapp_0123456789abcdef0123',

    'redirect_uri' => 'https://app.example.com/dzbuild/callback',

    'scope' => 'orders:read products:read',

    'state' => bin2hex(random_bytes(16)),

    'code_challenge' => $challenge,

    'code_challenge_method' => 'S256',

], '', '&', PHP_QUERY_RFC3986);
```

`PKCE` بلغة Node.js:

```
const crypto = require('node:crypto');



const verifier = crypto.randomBytes(32).toString('base64url'); // 43 characters

const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');



// Keep the verifier on your server (session or database) until the token call.

const authorizeUrl = 'https://dzbuild.com/oauth/apps/authorize?' + new URLSearchParams({

  response_type: 'code',

  client_id: 'dzapp_0123456789abcdef0123',

  redirect_uri: 'https://app.example.com/dzbuild/callback',

  scope: 'orders:read products:read',

  state: crypto.randomBytes(16).toString('hex'),

  code_challenge: challenge,

  code_challenge_method: 'S256',

});
```

لاختبار شيفرتك: متحقق المثال في `RFC 7636`، أي `dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk`، يجب أن يعطي التحدّي `E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM`.

## الخطوة 2: شاشة الموافقة[​](#الخطوة-2-شاشة-الموافقة "رابط مباشر إلى الخطوة 2: شاشة الموافقة")

تعرض شاشة الموافقة اسم تطبيقك وشعاره واسم المطوّر. يحمل التطبيق المقبول شارة "راجعته DZBuild". أما التطبيق غير المقبول بعد فتظهر عليه ملاحظة وضع التجربة، ولا يفتحه إلا مطوّره.

يرى التاجر المتاجر التي يملكها. أعضاء الفريق لا يرون أي متجر، لأن التثبيت يمنح رمزا للمتجر كله. المتجر الذي لا يمكنه استقبال التطبيق يظهر مع السبب: التاجر ليس المالك، أو التطبيق في وضع التجربة، أو خطة المتجر أدنى من الخطة الدنيا للتطبيق، أو التطبيق موقوف. يستطيع التاجر الموافقة على 10 متاجر كحد أقصى في المرة الواحدة.

تظهر الصلاحيات المطلوبة مجمّعة حسب المورد. يبقى طلب الموافقة صالحا 10 دقائق.

## الخطوة 3: استقبال إعادة التوجيه[​](#الخطوة-3-استقبال-إعادة-التوجيه "رابط مباشر إلى الخطوة 3: استقبال إعادة التوجيه")

بعد الموافقة، تجيب `DZBuild` بـ `302` نحو عنوان إعادة التوجيه الخاص بك:

```
HTTP/1.1 302 Found

Location: https://app.example.com/dzbuild/callback?code=3f9a...64-hex...&state=c2a8a879c9434b775191caf5b17d846f
```

الرمز من 64 حرفا ست عشريا، صالح 10 دقائق ويُستعمل مرة واحدة. تحقّق من مطابقة `state` للقيمة المحفوظة قبل استعمال الرمز. إذا كان عنوان إعادة التوجيه المسجّل يحمل سلسلة استعلام، تضيف `DZBuild` معاملاتها بعد `&`.

### أخطاء التفويض والموافقة[​](#أخطاء-التفويض-والموافقة "رابط مباشر إلى أخطاء التفويض والموافقة")

تصلك الأخطاء بطريقتين. قبل أن تتعرّف `DZBuild` على `client_id` و `redirect_uri`، تعرض صفحة خطأ للتاجر ولا تعيد التوجيه أبدا، حتى لا يرسل رابط خاطئ رموزا إلى عنوان مجهول. بعد ذلك تعيد التوجيه إلى `redirect_uri` مع المعامل `error`. تجري الفحوص بترتيب هذا الجدول.

| الحالة                                                                                 | النتيجة                           | طريقة الإرجاع                          |
| -------------------------------------------------------------------------------------- | --------------------------------- | -------------------------------------- |
| `client_id` مجهول أو غير صالح الصيغة، أو `redirect_uri` غير مسجّل حرفيا                | صفحة خطأ، `HTTP 400`              | صفحة للتاجر                            |
| التطبيق غير مقبول والتاجر ليس مطوّره، أو التطبيق مرفوض أو موقوف                        | صفحة خطأ، `HTTP 404`              | صفحة للتاجر                            |
| `response_type` ليس `code`                                                             | `error=unsupported_response_type` | إعادة توجيه، مع `state` إذا كانت صالحة |
| `state` غائبة أو أطول من 1024 حرفا                                                     | `error=invalid_request`           | إعادة توجيه، دون `state`               |
| `code_challenge` ليس 43 حرفا بترميز `base64url`، أو `code_challenge_method` ليس `S256` | `error=invalid_request`           | إعادة توجيه، مع `state`                |
| صلاحية غير مسجّلة على التطبيق أو غير مسموح بها للتطبيقات، أو `scope` أطول من 512 حرفا  | `error=invalid_scope`             | إعادة توجيه، مع `state`                |
| التاجر ينقر على الرفض                                                                  | `error=access_denied`             | إعادة توجيه، مع `state`                |
| لم يُختر أي متجر                                                                       | صفحة خطأ، `HTTP 400`              | صفحة للتاجر                            |
| اختيار أكثر من 10 متاجر                                                                | صفحة خطأ، `HTTP 400`              | صفحة للتاجر                            |
| متجر مختار لا يملكه التاجر أو لا يمكنه استقبال التطبيق                                 | صفحة خطأ، `HTTP 403`              | صفحة للتاجر                            |
| طلب الموافقة منتهي الصلاحية أو سبقت معالجته                                            | صفحة خطأ، `HTTP 400`              | صفحة للتاجر                            |

## الخطوة 4: تبادل الرمز[​](#الخطوة-4-تبادل-الرمز "رابط مباشر إلى الخطوة 4: تبادل الرمز")

يرسل خادمك نموذجا (`application/x-www-form-urlencoded`) إلى نقطة التبادل.

| الحقل           | القيمة                                        |
| --------------- | --------------------------------------------- |
| `grant_type`    | `authorization_code`                          |
| `code`          | الرمز الوارد في إعادة التوجيه.                |
| `redirect_uri`  | السلسلة نفسها التي أرسلتها إلى عنوان التفويض. |
| `code_verifier` | المتحقق الذي بُني عليه `code_challenge`.      |
| `client_id`     | معرّف العميل الخاص بك.                        |
| `client_secret` | سرّ العميل (`dzas_` يليه 48 حرفا ست عشريا).   |

يمكنك إرسال `client_id` و `client_secret` كبيانات اعتماد `HTTP Basic` بدل حقول النموذج. تقرأ `DZBuild` حقول النموذج أولا، ولا تستعمل الترويسة `Authorization: Basic` إلا إذا غاب الحقلان معا. داخل `Basic`، رمّز القيمتين بصيغة `form-urlencoded` كما يشترط القسم 2.3.1 من `RFC 6749`.

أرسل الترويسة `User-Agent` مع هذا الطلب ومع كل استدعاء للواجهة، مثلا `my-app/1.0 (+https://example.com)`. يجيب `dzbuild.com` على أي طلب `POST` لا يحملها، بما فيه طلب الرمز هذا، بـ `403` وصفحة تحقق بصيغة `HTML` بدل `JSON`. الدالة `fetch` في `Cloudflare Workers` وعدد من مكتبات `HTTP` لا ترسل `User-Agent` ما لم تضبطه بنفسك، أما `curl` فيرسل ترويسته الخاصة.

```
curl -sS https://dzbuild.com/oauth/apps/token \

  -H "Accept: application/json" \

  --data-urlencode "grant_type=authorization_code" \

  --data-urlencode "code=$CODE" \

  --data-urlencode "redirect_uri=https://app.example.com/dzbuild/callback" \

  --data-urlencode "code_verifier=$CODE_VERIFIER" \

  --data-urlencode "client_id=$DZBUILD_CLIENT_ID" \

  --data-urlencode "client_secret=$DZBUILD_CLIENT_SECRET"
```

التبادل الناجح يجيب بـ `200` مع `Cache-Control: no-store`:

```
{

  "access_token": "dzpk_live_...",

  "token_type": "Bearer",

  "scope": "orders:read products:read",

  "store_id": 141,

  "install_id": 57,

  "stores": [

    {

      "store_id": 141,

      "store_name": "Boutique Amel",

      "install_id": 57,

      "access_token": "dzpk_live_..."

    },

    {

      "store_id": 152,

      "store_name": "Amel Kids",

      "install_id": 58,

      "access_token": "dzpk_live_..."

    }

  ]

}
```

| الحقل                    | المعنى                                                                                    |
| ------------------------ | ----------------------------------------------------------------------------------------- |
| `access_token`           | رمز العنصر الأول في `stores`.                                                             |
| `token_type`             | دائما `Bearer`.                                                                           |
| `scope`                  | الصلاحيات الممنوحة، يفصل بينها فراغ. كل متجر في الرد يحصل على الصلاحيات نفسها.            |
| `store_id`، `install_id` | العنصر الأول في `stores`.                                                                 |
| `stores`                 | عنصر لكل متجر ثُبّت عليه التطبيق: `store_id`، `store_name`، `install_id`، `access_token`. |

لا يحمل الرد `expires_in` ولا `refresh_token`. احفظ كل رمز على الخادم، مفهرسا بـ `store_id`. يُسمّى رمز الوصول هذا الخاص بكل متجر رمز التثبيت في الصفحات الأخرى.

### أخطاء التبادل[​](#أخطاء-التبادل "رابط مباشر إلى أخطاء التبادل")

كل خطأ رد `JSON` يحمل `error` و `error_description`، مع `Cache-Control: no-store`.

| HTTP | `error`                  | `error_description`                                                          | السبب                                                                                                                    |
| ---- | ------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| 401  | `invalid_client`         | `Client authentication failed`                                               | `client_id` أو `client_secret` غائب أو خاطئ. مع بيانات `Basic` يحمل الرد أيضا `WWW-Authenticate: Basic realm="dzbuild"`. |
| 400  | `unsupported_grant_type` | `Only authorization_code is supported`                                       | `grant_type` ليس `authorization_code`.                                                                                   |
| 400  | `invalid_grant`          | `The code is invalid, expired, already used, or does not match this request` | رمز مجهول أو منتهي أو مستعمل، أو رمز صادر لتطبيق آخر، أو `redirect_uri` مختلف، أو متحقق لا يطابق التحدّي.                |
| 400  | `invalid_grant`          | `No approved store could be installed`                                       | تعذّر تثبيت أي متجر مختار: كل متجر فشل في الفحص الأخير (مثلا خطة أدنى من الخطة الدنيا للتطبيق) أو فشل تثبيته.            |
| 500  | `server_error`           | `The code could not be checked` أو `The install could not be completed`      | عطل في المنصّة. أعد التاجر إلى خطوة التفويض.                                                                             |

تتحقق `DZBuild` من العميل أولا، ثم من `grant_type`، ثم تستهلك الرمز. الرمز المستهلك يضيع حتى لو كان `redirect_uri` أو المتحقق خاطئا، فلا يمكن إعادة محاولة تبادل فاشل بالرمز نفسه. ابدأ طلب تفويض جديدا.

## التثبيت على عدة متاجر[​](#التثبيت-على-عدة-متاجر "رابط مباشر إلى التثبيت على عدة متاجر")

التاجر الذي يملك عدة متاجر يستطيع الموافقة على 10 منها كحد أقصى في موافقة واحدة. يذكر رد التبادل كل متجر ثُبّت عليه التطبيق في `stores`، مع `install_id` و `access_token` خاصين به. الرمز لا يصل إلا إلى متجره.

تفحص `DZBuild` كل متجر مرة أخرى أثناء التبادل. المتجر الذي لم يعد يستوفي الشروط يُستبعد، لذا قد تحمل `stores` متاجر أقل مما اختاره التاجر. اعتمد على `stores` لا على الموافقة التي توقّعتها.

إعادة المسار لمتجر ثُبّت عليه تطبيقك من قبل تحدّث التثبيت نفسه: يبقى `install_id`، وتصبح الصلاحيات هي صلاحيات الموافقة الجديدة، ويحلّ رمز جديد محلّ القديم. يتوقف الرمز القديم فورا.

## استدعاء الواجهة[​](#استدعاء-الواجهة "رابط مباشر إلى استدعاء الواجهة")

أرسل الرمز في ترويسة `Bearer` إلى `https://api.dzbuild.app/v1`. الطلب `GET /v1/whoami` لا يحتاج أي صلاحية ويبيّن ما يستطيعه الرمز:

```
curl -sS https://api.dzbuild.app/v1/whoami \

  -H "Authorization: Bearer $DZBUILD_ACCESS_TOKEN"
```

```
{

  "data": {

    "key_id": "...",

    "store_id": 141,

    "type": "platform",

    "rate_limit_tier": "enterprise",

    "pilot": true,

    "scopes": ["orders:read", "products:read"],

    "app": {

      "app_id": 12,

      "client_id": "dzapp_0123456789abcdef0123",

      "install_id": 57

    }

  },

  "meta": {

    "request_id": "...",

    "api_version": "v1"

  }

}
```

رموز التطبيقات تعمل مع كل خطط المتاجر. المستوى `enterprise` الظاهر في `whoami` هو التسمية التي تعطيها الواجهة لرموز التطبيقات، أما حدود تطبيقك فهي في صفحة [حدود معدّل الطلبات](https://dzbuild.dev/ar/ar/rate-limits.md).

## مدة صلاحية الرمز وإلغاؤه[​](#مدة-صلاحية-الرمز-وإلغاؤه "رابط مباشر إلى مدة صلاحية الرمز وإلغاؤه")

ليس لرمز الوصول تاريخ انتهاء. يبقى صالحا حتى يقع أحد الأمرين:

* يزيل التاجر التطبيق. يُلغى الرمز وتجيب الاستدعاءات بـ `401` مع رمز الخطأ `unauthorized` والرسالة `Invalid or revoked API key`. تستقبل نقطة `webhook` التي تم التحقق منها حدثا واحدا `app.uninstalled`.
* تعيد مسار التثبيت للمتجر نفسه. يحلّ الرمز الجديد محلّ القديم.

ما دام التثبيت قائما، ترفض الواجهة الرمز بـ `403` في هذه الحالات:

| `error.code`        | الرسالة                                                            | متى                                                                                     |
| ------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
| `app_uninstalled`   | `This app is no longer installed on this store`                    | التثبيت لم يعد نشطا.                                                                    |
| `app_suspended`     | `This app has been suspended by DZBuild`                           | أوقفت `DZBuild` التطبيق أو رفضته. تعود الاستدعاءات للعمل بعد رفع الإيقاف.               |
| `app_not_approved`  | `This app is in test mode and only runs on its developer's stores` | لم يُقبل التطبيق بعد (مسودة أو في مراجعته الأولى) والمتجر ليس ملكا لمطوّره.             |
| `app_plan_required` | `This app requires the ... plan`                                   | خطة المتجر، أو خطة مدفوعة منتهية، أدنى من الخطة الدنيا للتطبيق. تذكر الرسالة اسم الخطة. |

عبر `api.dzbuild.app`، تمرّر البوابة رد `DZBuild` كما هو دون أي تعديل، فتصلك رموز `403` أعلاه وكذلك `401` و`429` بالحالة والمحتوى الموصوفين هنا. وإذا تعذّر على البوابة التحقق من الرمز لدى `DZBuild`، لأنها لا تستطيع الوصول إليها أو لأنها ردّت بخطأ في الخادم، فإنها تجيب بـ `502` مع رمز الخطأ `server_error` والرسالة `Key lookup failed, retry shortly`. أعد المحاولة بعد لحظات.

لا توجد في الإصدار الأول نقطة وصول تلغي بها التطبيقات رموزها. إزالة التطبيق يقوم بها التاجر من صفحة التطبيق في لوحة التحكم.

تجديد سرّ العميل في منصة المطورين يستبدله فورا. الرموز الصادرة من قبل تبقى صالحة.

## حدود معدّل الطلبات[​](#حدود-معدّل-الطلبات "رابط مباشر إلى حدود معدّل الطلبات")

| نقطة الوصول                 | الحد                                                   |
| --------------------------- | ------------------------------------------------------ |
| `GET /oauth/apps/authorize` | 30 طلبا كل 300 ثانية                                   |
| `POST /oauth/apps/approve`  | 10 طلبات كل 600 ثانية                                  |
| `POST /oauth/apps/token`    | 60 طلبا كل 300 ثانية                                   |
| واجهة `REST` برمز تطبيق     | 120 طلبا في الدقيقة لكل تثبيت، قبل الحد المشترك للمتجر |

تُحسب حدود `OAuth` لكل عميل، ويُعرَّف العميل بعنوان `IP` وترويسات المتصفح والجلسة. التزم بها: العميل الذي يتجاوز حدًا قد يتلقى `429` مع ترويسة `Retry-After` لا تتجاوز 120 ثانية. أرسل `Accept: application/json` في طلبات التبادل حتى يعود الرفض بصيغة `JSON` لا كإعادة توجيه. حدود الواجهة في صفحة [حدود معدّل الطلبات](https://dzbuild.dev/ar/ar/rate-limits.md).
