مسار التثبيت عبر 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/... | خادمك، مع رمز الوصول |
خطوات المسار
- ينشئ خادمك متحقق
PKCEوتحدّيه بطريقةS256، وقيمةstateعشوائية. - يوجّه التاجر إلى عنوان التفويض. التاجر غير المسجّل يدخل حسابه أولا ثم يعود إلى العنوان نفسه.
- تعرض
DZBuildشاشة الموافقة. يختار التاجر المتاجر ويوافق. - تعيد
DZBuildتوجيه المتصفح إلىredirect_uriالخاص بك معcodeوقيمةstate. - يرسل خادمك الرمز والمتحقق وبيانات اعتماد العميل إلى نقطة التبادل.
- يحمل الرد رمز وصول لكل متجر ثُبّت عليه التطبيق.
الخطوة 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: شاشة الموافقة
تعرض شاشة الموافقة اسم تطبيقك وشعاره واسم المطوّر. يحمل التطبيق المقبول شارة "راجعته DZBuild". أما التطبيق غير المقبول بعد فتظهر عليه ملاحظة وضع التجربة، ولا يفتحه إلا مطوّره.
يرى التاجر المتاجر التي يملكها. أعضاء الفريق لا يرون أي متجر، لأن التثبيت يمنح رمزا للمتجر كله. المتجر الذي لا يمكنه استقبال التطبيق يظهر مع السبب: التاجر ليس المالك، أو التطبيق في وضع التجربة، أو خطة المتجر أدنى من الخطة الدنيا للتطبيق، أو التطبيق موقوف. يستطيع التاجر الموافقة على 10 متاجر كحد أقصى في المرة الواحدة.
تظهر الصلاحيات المطلوبة مجمّعة حسب المورد. يبقى طلب الموافقة صالحا 10 دقائق.
الخطوة 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: تبادل الرمز
يرسل خادمك نموذجا (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 هو التسمية التي تعطيها الواجهة لرموز التطبيقات، أما حدود تطبيقك فهي في صفحة حدود معدّل الطلبات.
مدة صلاحية الرمز وإلغاؤه
ليس لرمز الوصول تاريخ انتهاء. يبقى صالحا حتى يقع أحد الأمرين:
- يزيل التاجر التطبيق. يُلغى الرمز وتجيب الاستدعاءات بـ
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 لا كإعادة توجيه. حدود الواجهة في صفحة حدود معدّل الطلبات.