واجهة REST و OAuth

صادِق عبر OAuth 2.0 واقرأ أو اكتب بيانات متجر التاجر عبر HTTPS.

نظرة عامة

واجهة المطورين بتخلّي تطبيق طرف-ثالث يقرأ ويكتب بيانات متجر التاجر عبر HTTPS. الصلاحية بيمنحها التاجر نفسه مش إنت: هو يثبّت تطبيقك، يراجع الأذونات اللي بتطلبها، ويقدر يلغيها في أي وقت.

  • الرابط الأساسي: https://{platform-domain}/api/v1
  • المصادقة: OAuth 2.0 بمنح authorization-code، PKCE إلزامي
  • الصيغة: JSON دخولًا وخروجًا (الواجهة دايمًا بترد JSON)
  • حد المعدل: 120 طلب/دقيقة لكل توكن

1. سجّل تطبيقك

من كونسول المطور روح تطبيقاتي (/developer/apps) واعمل تطبيق. بتدخل:

الحقل ملاحظات
الاسم والوصف والموقع بتظهر للتاجر في شاشة الموافقة
Redirect URIs واحد في كل سطر. لازم https:// (http:// مسموح على localhost). بتتطابق حرفيًا وقت التفويض
الصلاحيات (Scopes) الأذونات اللي تطبيقك ممكن يطلبها

هتستلم client_id وclient_secret. السر بيظهر مرة واحدة ومش قابل للاسترجاع أبدًا — بيتخزن الهاش بتاعه بس. ضاع منك؟ استخدم تدوير السر (القديم يموت فورًا).

2. الصلاحيات

اطلب أقل حاجة تحتاجها — التاجر بيشوف كل صلاحية في شاشة الموافقة.

الصلاحية تمنح
products.read عرض المنتجات والمتغيرات والمخزون
products.write إنشاء وتعديل وحذف المنتجات
categories.read عرض الفئات
categories.write إنشاء وتعديل وحذف الفئات
orders.read عرض الطلبات وعناصرها
orders.write تحديث حالة الطلب والتنفيذ
customers.read عرض العملاء وبيانات التواصل
webhooks.manage إدارة اشتراكات الـwebhook

3. وجّه التاجر لشاشة الموافقة

جهّز زوج PKCE الأول:

code_verifier  = نص عشوائي 43–128 حرف  (سري، على الخادم)
code_challenge = BASE64URL( SHA256(code_verifier) )

وبعدين وجّه التاجر لـ:

GET https://{platform-domain}/oauth/authorize
      ?response_type=code
      &client_id=dapi_xxxxxxxx
      &redirect_uri=https://yourapp.com/oauth/callback
      &scope=products.read%20orders.read
      &state=RANDOM_ANTI_CSRF_VALUE
      &code_challenge=BASE64URL_SHA256_OF_VERIFIER
      &code_challenge_method=S256

التاجر يسجّل دخول ويوافق. بنرجّعه لـredirect_uri بتاعك:

https://yourapp.com/oauth/callback?code=ONE_TIME_CODE&state=RANDOM_ANTI_CSRF_VALUE

تحقق دايمًا إن state مطابق للي بعتّه. لو التاجر رفض، هتستلم ?error=access_denied بدل الكود.

لو client_id أو redirect_uri غير صالح بنعرض صفحة خطأ وما بنوجّهش — إحنا أبدًا ما بنبعت كود لوجهة غير مسجّلة.

4. بدّل الكود بتوكنات

curl -X POST https://{platform-domain}/oauth/token \
  -d grant_type=authorization_code \
  -d client_id=dapi_xxxxxxxx \
  -d client_secret=dapi_sec_xxxxxxxx \
  -d code=ONE_TIME_CODE \
  -d redirect_uri=https://yourapp.com/oauth/callback \
  -d code_verifier=YOUR_ORIGINAL_VERIFIER
{
  "access_token": "dapi_at_…",
  "refresh_token": "dapi_rt_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "products.read orders.read"
}

الكود يُستخدم مرة واحدة وينتهي خلال 10 دقائق. إعادة استخدامه، أو code_verifier غلط، أو redirect_uri غير مطابق — كلها ترجّع invalid_grant.

بيانات اعتماد العميل ممكن كمان تتبعت عبر HTTP Basic بدل حقول الفورم.

التجديد

توكن الوصول بيعيش ساعة، وتوكن التجديد 30 يوم. التجديد بيدوّر الزوج — التوكنات القديمة تتبطّل فورًا:

curl -X POST https://{platform-domain}/oauth/token \
  -d grant_type=refresh_token \
  -d client_id=dapi_xxxxxxxx \
  -d client_secret=dapi_sec_xxxxxxxx \
  -d refresh_token=dapi_rt_…

الإبطال

curl -X POST https://{platform-domain}/oauth/revoke \
  -d client_id=… -d client_secret=… -d token=dapi_at_…

بيرجّع دايمًا 200 (حسب RFC 7009)، حتى لتوكن غير معروف.

5. نادِ الواجهة

ابعت توكن الوصول كـBearer:

curl https://{platform-domain}/api/v1/products \
  -H "Authorization: Bearer dapi_at_…"

اعرف التوكن بتاع أنهي متجر:

GET /api/v1/me
{
  "data": {
    "application": { "client_id": "dapi_…", "name": "تطبيقك" },
    "store": { "user_id": 42, "name": "متجر أكمي", "email": "owner@acme.com" },
    "scopes": ["products.read"],
    "expires_at": "2026-07-18T12:58:04+00:00"
  }
}

6. النقاط (Endpoints)

كل نقطة مقصورة على التاجر اللي منح التوكن — مستحيل تشوف أو تلمس بيانات متجر تاني.

المنتجات — products.read / products.write

الميثود المسار ملاحظات
GET /api/v1/products فلاتر: search, category_id, is_active, per_page
GET /api/v1/products/{id}
POST /api/v1/products name وprice مطلوبين
PUT /api/v1/products/{id}
DELETE /api/v1/products/{id}
curl -X POST https://{platform-domain}/api/v1/products \
  -H "Authorization: Bearer dapi_at_…" \
  -H "Content-Type: application/json" \
  -d '{"name":"Blue Widget","price":99.50,"stock_quantity":7,"type":"physical"}'

type بيقبل physical (الافتراضي) أو digital. وcategory_id لازم تكون تابعة لنفس المتجر.

الفئات — categories.read / categories.write

الميثود المسار
GET /api/v1/categories (فلاتر: parent_id, is_active)
GET /api/v1/categories/{id}
POST /api/v1/categories
PUT /api/v1/categories/{id}
DELETE /api/v1/categories/{id}

الطلبات — orders.read / orders.write

الميثود المسار ملاحظات
GET /api/v1/orders فلاتر: status, payment_status, created_after
GET /api/v1/orders/{id} بتشمل عناصر الطلب
PATCH /api/v1/orders/{id} status, payment_status, notes فقط

status: pending, processing, completed, canceled. payment_status: pending, paid, failed.

حقول الفلوس مش قابلة للكتابة. الإجماليات بتحسبها خطوط تسعير المنصة نفسها.

العملاء — customers.read (قراءة فقط)

الميثود المسار
GET /api/v1/customers (فلتر: search)
GET /api/v1/customers/{id}

العملاء بيانات شخصية: المورد ده للقراءة فقط وما بيكشفش كلمات مرور ولا أرصدة محافظ.

الـWebhooks — webhooks.manage

الميثود المسار
GET /api/v1/webhooks
POST /api/v1/webhooks
DELETE /api/v1/webhooks/{id}

7. الترقيم (Pagination)

نقاط القوائم بترجّع data وmeta. استخدم per_page (أقصى 100، الافتراضي 25) وpage.

{
  "data": [ … ],
  "meta": { "current_page": 1, "per_page": 25, "total": 134, "last_page": 6 }
}

8. الـWebhooks

اشترك في حدث وإحنا بنبعتهولك على نقطتك ساعة ما يحصل.

curl -X POST https://{platform-domain}/api/v1/webhooks \
  -H "Authorization: Bearer dapi_at_…" \
  -H "Content-Type: application/json" \
  -d '{"event":"order.created","target_url":"https://yourapp.com/hooks/orders"}'

الرد بيحتوي على signing_secretبيظهر مرة واحدة، خزّنه.

الأحداث: order.created, order.updated, product.created, product.updated, customer.created.

الـtarget_url لازم تكون https://.

شكل التسليم

POST /hooks/orders
X-Dropsaas-Event: order.created
X-Dropsaas-Event-Id: evt_abc123…
X-Dropsaas-Signature: t=1752835200,v1=9f86d0818…
Content-Type: application/json

{ "id": "evt_abc123…", "event": "order.created",
  "created_at": "2026-07-18T12:00:00+00:00", "data": { … } }

التحقق من التوقيع

أعد حساب الـHMAC على "{timestamp}.{raw_request_body}":

[$t, $v1] = // فكّ "t=…,v1=…" من X-Dropsaas-Signature
$expected = hash_hmac('sha256', $t.'.'.$rawBody, $signingSecret);

if (! hash_equals($expected, $v1)) {
    abort(400); // ارفض — مش مننا
}

// ارفض أي حاجة أقدم من ~5 دقائق لمنع إعادة التشغيل.
if (abs(time() - (int) $t) > 300) {
    abort(400);
}

الطابع الزمني جوه النص الموقّع، فمهاجم ما يقدرش يعيد إرسال جسم ملتقَط بطابع زمني جديد.

إعادة المحاولة

رجّع 2xx للإقرار. أي رد غير 2xx أو انتهاء مهلة بيتعاد بتراجع (10ث، 1د، 5د، 15د، 1س — 5 محاولات). بعد 10 إخفاقات متتالية الاشتراك بيتعطّل تلقائيًا. استخدم X-Dropsaas-Event-Id لمنع التكرار: نفس معرّف الحدث ممكن يوصل أكتر من مرة.

9. الأخطاء

الحالة error المعنى
401 invalid_token توكن ناقص أو تالف أو منتهي أو مُبطَل
401 invalid_client فشلت مصادقة العميل على /oauth/token
400 invalid_grant كود غلط/مستخدم، أو PKCE أو redirect_uri غلط، أو refresh ميت
400 unsupported_grant_type فقط authorization_code وrefresh_token
403 insufficient_scope التوكن ما يحملش الصلاحية المطلوبة (الرد بيسمّيها)
404 not_found مفيش سجل بالمعرّف ده في المتجر ده
422 فشل التحقق؛ شوف errors
429 تجاوزت حد المعدل
{ "error": "insufficient_scope",
  "error_description": "This token does not carry the required scope.",
  "required_scope": "orders.read" }

10. ملاحظات أمنية

  • ما تشحنش الـclient_secret في تطبيق موبايل أو حزمة واجهة أمامية. PKCE بيحمي تبديل الكود، لكن السر للعملاء السرّيين (على الخادم).
  • خزّن التوكنات مشفّرة؛ عاملها زي كلمات المرور.
  • تحقق من state في كل callback لمنع CSRF.
  • تحقق من توقيع الـwebhook في كل تسليم — ما تثقش في الجسم لوحده أبدًا.
  • التاجر يقدر يلغي وصولك فورًا من التطبيقات المتصلة؛ تعامل مع 401 invalid_token بإعادة تشغيل تدفّق التفويض.