تخط وانتقل إلى المحتوى الرئيسي

للموزّعين

كيف تستخدم DZBuild API لإعادة بيع المتاجر، إدارة العملاء على نطاق واسع، والتكامل مع back-office الخاص بك.

بقلم: Support

إن كنت تبني متاجر للعملاء — وكالات، فريلانسرز، شركات تنفيذ، مشغّلو منصات سوق — فإن DZBuild API مصمّم ليكون عمودك العملياتي.

هذا الدليل لك إن كان أيٌّ مما يلي ينطبق:

  • تدير 5+ متاجر DZBuild لعملاء مختلفين.

  • تُفوتر العملاء لكل طلب أو شهريًا وتحتاج إشارة استخدام موثوقة.

  • تبني ثيمات مخصصة أو واجهات مخصصة نيابة عن العملاء.

  • تبيع DZBuild كطبقة علامة بيضاء ("MyAgency Commerce, مدعومة من DZBuild").

  • تريد سكربتة عمليات بالجملة: استيراد منتجات، تحديث أسعار، تأكيد طلبات.

كيف يستخدم الموزّعون الـ API فعلًا

إعداد موزّع نموذجي:

                                ┌─────────────────────────┐
                                │ back-office /           │
                                │ لوحة وكالتك              │
                                │ (كودك، واجهتك)           │
                                └──────────┬──────────────┘
                                           │  Bearer dzpk_live_…
                                           ▼
                            ┌─────────────────────────────┐
                            │  api.dzbuild.app/v1/*       │
                            └──────────┬──────────────────┘
                                       ▼
                           ┌──────────┬─────────┬──────────┐
                           │ متجر A  │ متجر B │ متجر C  │   ← العملاء الذين تديرهم
                           └──────────┴─────────┴──────────┘

كل عميل له متجر DZBuild خاص (حساب التاجر، خطته، طلباته). تحمل مفتاح API لكل متجر وتنسّق كل شيء — إعداد، تخصيص، تقارير — من لوحة وكالتك.

نموذج المفاتيح للموزّعين

لا يوجد نوع مفتاح "موزّع" خاص، ولا مفتاح عابر للمتاجر. كل موزّع يعمل بـ مفتاح منصة واحد لكل متجر عميل، وPOST /v1/keys مقيّد بصرامة بالمتجر الذي ينتمي إليه المفتاح المنادي أصلًا.

أ) أول مفتاح للعميل تُصدره DZBuild ثم يُسلَّم إليك

تُنشأ المفاتيح من لوحة تحكم التاجر عبر الإعدادات ← واجهة API (/dashboard/api) بواسطة مالك المتجر (خطة Enterprise، وبحد أقصى 3 مفاتيح نشطة لكل متجر) — فإدماج عميل جديد يعني أن يُنشئ العميل مفتاحًا ويسلّمه لك، أو تُنشئانه معًا. وما دامت المرحلة التجريبية قائمة، يستطيع دعم DZBuild أيضًا إصدار المفاتيح وتسجيلها. تُخزّن أسرار العملاء في back-office لديك مشفّرة، وكل عملية ضد العميل X تستخدم مفتاح العميل X.

الإيجابيات: موافقة واضحة، العميل يملك المفتاح ويستطيع إلغاءه. السلبيات: احتكاك في التأهيل — خطوة بشرية لكل متجر.

ب) تُنشئ بنفسك مفاتيح إضافية لمتجر لديك مفتاح له أصلًا

ما إن يكون لديك مفتاح واحد لمتجر، يمكنك إنشاء المزيد لذلك المتجر نفسه دون مراسلة أحد:

curl -X POST 'https://api.dzbuild.app/v1/keys' \
  -H "Authorization: Bearer $CLIENT_X_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"type":"platform","name":"agency-reporting"}'
# HTTP 200 → { "data": { "key_id", "bearer_token", "signing_secret", "note" } }

يرث المفتاح الجديد فئة تحديد المعدل وعَلَم البرنامج التجريبي من المنادي. مفيد لفصل مهام التقارير عن مهام الكتابة على المتجر نفسه.

ما هو غير موجود: لا توجد أي تهيئة "مظلة وكالة"، على أي خطة، تُتيح لك إنشاء مفاتيح لمتاجر لا تملك مفتاحًا لها أصلًا. وتأهيل متجر عميل جديد يبدأ دائمًا بإصدار DZBuild لأول مفتاح لذلك المتجر.

استيراد منتجات بالجملة

تبني كتالوجًا لعميل؟ استخدم حلقة CSV → API:

import requests, csv, uuid, osKEY = os.environ['DZ_KEY_CLIENT_X']
HEADERS = {
    'Authorization': f'Bearer {KEY}',
    'Content-Type':  'application/json',
}with open('products.csv') as f:
    for row in csv.DictReader(f):
        body = {
            'name':           row['name'],
            'price':          float(row['price']),
            'compare_price':  float(row['compare_price']) if row['compare_price'] else None,
            'sku':            row['sku'],
            'description':    row['description'],
            'stock_quantity': int(row['stock']),
            'track_stock':    True,
            'status':         'active',
        }
        r = requests.post(
            'https://api.dzbuild.app/v1/products',
            headers={**HEADERS, 'Idempotency-Key': str(uuid.uuid4())},
            json=body,
        )
        if r.ok:                       # 200، وليس 201 — لا إنشاء في v1 يُرجع 201
            print(f"OK {row['sku']} → id {r.json()['data']['id']}")
        else:
            print(f"FAIL {row['sku']}: {r.text}")

خطآن يجب تفاديهما في تلك الحلقة، وكلاهما موجود في النسخة الساذجة:

  • لا تفحص 201. فـ POST /v1/products يُرجع 200؛ وفحص == 201 يطبع FAIL لكل منتج أنشأه بنجاح.

  • لا تستعمل uuid4() لمفتاح Idempotency. فمفتاح جديد في كل تشغيل يعني أن إعادة التشغيل تُنشئ نسخًا مكررة بدل إعادة تشغيل الاستجابة. استعمل مفتاحًا حتميًا مثل import-{client}-{sku}-{run} — وهو ما تقوله النصيحة أدناه أيضًا، فأبقِ الشيفرة والنصيحة متسقتين.

نصائح:

  • استخدم مفتاح Idempotency حتمي (مثل import-{client}-{sku}-{run}) فتقفز الإعادات على الصفوف المستوردة. وتُخزَّن استجابة كل مفتاح 24 ساعة، بما فيها الأخطاء — غيّر المفتاح بعد إصلاح طلب سيّئ.

  • كل متجر عميل تديره عبر الـ API يجب أن يكون على خطة Enterprise سارية (فالواجهة البرمجية حصرية لخطة Enterprise)، وهي تسمح بـ 600 طلب/دقيقة لكل متجر (مشتركة بين جميع مفاتيحه) — لذا يظل إنشاء ~1000 منتج يستغرق دقيقتين تقريبًا من الزمن الفعلي. سِر بنحو نصف السقف لتترك هامشًا؛ وتجاوزه يُرجع 429 rate_limited مع ترويسة Retry-After. ولاحظ أن صور المنتجات لها ميزانيتها الخاصة الأضيق — 10 في الدقيقة لكل متجر — فعمليات الاستيراد بالجملة المصحوبة بالصور تنضبط وتيرتها بها لا بالحد العام.

  • رفع الصور يمر عبر اللوحة حاليًا؛ الـ API سيدعم روابط الرفع presigned في v1.1.

عمليات طلبات بالجملة

⚠️ تنبيه — limit=200 هو السقف، لا "كل الطلبات"

limit مقيّد بـ 200. والسكربتان أدناه يتوقفان عند ذلك ولا يخبرانك بشيء عمّا فاتهما. وكل استجابة قائمة تحمل has_more و next_cursor — كرّر عليهما:

CURSOR=""
while :; do
  PAGE=$(curl -sS "https://api.dzbuild.app/v1/orders?status=pending&limit=200${CURSOR:+&cursor=$CURSOR}" \
    -H "Authorization: Bearer $KEY")
  echo "$PAGE" | jq -r '.data.items[].id'
  [ "$(echo "$PAGE" | jq -r '.data.has_more')" = "true" ] || break
  CURSOR=$(echo "$PAGE" | jq -r '.data.next_cursor')
done

كما أن قراءات الطلبات لا تُخزَّن مؤقتًا أبدًا (المخزَّن هو المنتجات وصفحات الهبوط والمتجر فقط)، فمسح 50 متجر عميل يعني 50 رحلة غير مخزَّنة، كل منها تُحسب من ميزانية ذلك المتجر في الدقيقة. استعمل نوافذ ?since= بدل إعادة قراءة كامل المتراكم.

تأكيد كل الطلبات pending لعميل واحد

KEY="$(cat /etc/secrets/client-x.key)"
# 1. اسرد pending
curl -sS 'https://api.dzbuild.app/v1/orders?status=pending&limit=200' \
  -H "Authorization: Bearer $KEY" \
| jq -r '.data.items[].id' \
| while read OID; do
    # 2. أكّد كل واحد
    curl -sS -X PATCH "https://api.dzbuild.app/v1/orders/$OID" \
      -H "Authorization: Bearer $KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: confirm-$OID" \
      -d '{"status":"confirmed"}'
  done

تقرير إيرادات يومي عبر كل العملاء

clients = json.load(open('clients.json'))  # [{name, key}, ...]
today = date.today().isoformat() + 'T00:00:00Z'for c in clients:
    orders = requests.get(
        f'https://api.dzbuild.app/v1/orders?since={today}&limit=200',
        headers={'Authorization': f'Bearer {c["key"]}'},
    ).json()['data']['items']
    revenue = sum(o['total'] for o in orders if o['status'] == 'delivered')
    print(f"{c['name']}: {len(orders)} طلبات، {revenue} دج")

العلامة البيضاء (White-labeling)

تريد أن تبدو متاجرك كأنها علامتك، لا DZBuild. الـ API يُمكّن ذلك:

  • ابنِ الواجهة بنفسك بأي ستاك/علامة (انظر الثيمات والواجهات المخصصة).

  • استخدم نطاقك الخاص (shop.yourbrand.com) — وجّهه إلى واجهتك المخصصة، وواجهتك تتحدث مع DZBuild عبر API.

  • لوحة التاجر في dzbuild.com/dashboard تبقى بعلامة DZBuild — هناك تؤكد الطلبات. عملاؤك النهائيون لا يرون علامة DZBuild أبدًا.

لا توجد لوحة تاجر بعلامة بيضاء، على أي خطة. وأداة العلامة الوحيدة الموجودة هي hide_branding (خطة Unlimited فما فوق)، وهي تزيل علامة DZBuild من واجهة متجر التاجر — لا من لوحة التحكم. وقيمتها الحالية مكشوفة على GET /v1/store. وإن احتجت ما هو أبعد من ذلك، تحدّث مع المبيعات بدل التخطيط اعتمادًا على منتج غير موجود بعد.

Webhooks للموزّعين

سجّل webhook واحد لكل متجر عميل يشير إلى back-office وكالتك:

curl -X POST 'https://api.dzbuild.app/v1/webhooks' \
  -H "Authorization: Bearer $CLIENT_X_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url":    "https://api.youragency.com/dz-hooks?store=client-x",
    "events": ["order.created","order.confirmed","order.shipped","order.delivered","order.cancelled"]
  }'

استعمل المعامل ?store=client-x فيمكن لنقطة واحدة معالجة كل العملاء — وأبقِ بقية المسار غير قابلة للتخمين، لأن تواقيع API v1 لا يمكن التحقق منها حاليًا بسر العميل (فهي ليست موقّعة به). أكّد أي أمر مهم بإعادة قراءة GET /v1/orders/{id} بمفتاح ذلك العميل. انظر التحقق من التواقيع.

وخطّط أيضًا لفجوة التغطية: order.created في API v1 يُطلق فقط للطلبات المُنشأة عبر POST /v1/orders، وأحداث الحالة فقط لتغييرات الحالة التي تتم عبر الـ API. فإن كان عملاؤك يتلقّون الطلبات على واجهة DZBuild ويؤكدونها من اللوحة، فلن ترى شيئًا هنا — إما أن تستعلم GET /v1/orders?since=... لكل متجر، أو أن يُفعّل كل عميل إضافة Webhooks بدون كود على /dashboard/webhooks (خطة Unlimited فما فوق)، فهي تغطي كل مصادر الطلبات.

الحسابات متعددة المتاجر

بعض العملاء يديرون عدة متاجر في حساب DZBuild واحد (ميزة Multi-store). كل متجر له مفتاحه — مستقلة. سمّها بوضوح في مدير الأسرار:

DZ_KEY_CLIENT_X_BRAND_A=dzpk_live_...
DZ_KEY_CLIENT_X_BRAND_B=dzpk_live_...

الخطط — ما توصي به

كل متجر تديره عبر الـ API يجب أن يكون على خطة Enterprise سارية. فالواجهة البرمجية حصرية لخطة Enterprise: على أي خطة أخرى — أو باشتراك Enterprise منتهي الصلاحية — يُرجع كل نداء 403 forbidden مهما كان صاحب المفتاح، ولا يمكن أصلًا إنشاء مفاتيح جديدة للمتجر.

أما متاجر العملاء التي لا تؤتمتها (تستعمل لوحة التحكم وواجهة المتجر فقط)، فتبقى الخطط الأخرى سارية كالمعتاد:

حجم العميل

الخطة المستحسنة

السبب

0–30 طلب/شهر

Free

تحقق من الفكرة قبل الالتزام — لكن الخطة Free تحدّ المتجر بـ 30 طلبًا في الشهر الميلادي، وبعدها يُرجع POST /v1/orders الخطأ 400 bad_request "Monthly order limit reached for this store plan"

30–500 طلب/شهر

Pro

يُزيل سقف الطلبات الشهري

500–2000 طلب/شهر

Unlimited

بلا سقف طلبات، إضافة إلى hide_branding على واجهة المتجر

2000+ / multi-brand / أي أتمتة عبر الـ API

Enterprise

عقد مخصص، دعم أولوي — وهي الخطة الوحيدة التي تملك وصولًا إلى الـ API

ℹ️ معلومة — الخطة هي بوابة الوصول إلى الـ API

خطة اشتراك المتجر هي بوابة وصوله إلى الـ API: المتاجر ذات خطة Enterprise السارية وحدها تستطيع إنشاء المفاتيح وإجراء نداءات الـ API‏ (600 طلب/دقيقة لكل متجر، مشتركة بين مفاتيحه — انظر حدود المعدل). خفّض خطة عميل، أو دَع اشتراكه في Enterprise ينتهي، فتتوقف مفاتيحه فورًا (403 forbidden)؛ وأعِده إلى Enterprise فتعود المفاتيح نفسها إلى العمل — دون أي إعادة إصدار. ويظل التسجيل في البرنامج التجريبي مطلوبًا ما دام البرنامج قائمًا — وبدونه يُرجع كل نداء 403 forbidden "API is in pilot mode; key not enrolled".

فوترة عملائك

الـ API يُعطيك إشارات استخدام دقيقة لتفوتر بثقة:

  • GET /v1/usage — نداءات الشهر الحالي مقسمة حسب مجموعة النقاط

  • GET /v1/usage/history — صفوف ساعية (period_hour و endpoint_group و count و billable_count) تحت data.rows. تفترض آخر 7 أيام افتراضيًا وتقبل نافذة 90 يومًا كحد أقصى عبر ?from=&to=؛ وأي نافذة أوسع تُرجع 400 bad_request "range too large (max 90 days)". أي أن سنة كاملة تعني المرور عبر خمس نوافذ من 90 يومًا.

  • GET /v1/orders?status=delivered&since=... — لنماذج عمولة لكل طلب (وتذكّر سقف 200 صف لكل صفحة و next_cursor)

معظم الموزّعين يفوترون:

  • رسوم شهرية ثابتة لاستضافة الواجهة + إعادة بيع رخصة DZBuild

  • % عمولة على الطلبات المُسلَّمة (اقرأ من ?status=delivered)

  • أو اشتراك مدمج (مثل "10000 دج/شهر للكل")

اختر النموذج الذي يتوقعه سوقك.

نصائح تشغيلية

  • بيئة لكل عميل. أسرار dev في dev. أسرار prod في prod. لا تشارك أبدًا.

  • استخدم Idempotency-Keys حتمية لأي حلقة إعادة (خصوصًا الاستيراد بالجملة / التأكيد بالجملة).

  • خزّن قراءات المنتجات. تُخزَّن GET /v1/products و GET /v1/landing-pages و GET /v1/store مؤقتًا لمدة 30 ثانية، ويمكنك إضافة طبقة 5 دقائق فوقها — المنتجات نادرًا ما تتغير لحظيًا. أما قراءات الطلبات فلا تُخزَّن مؤقتًا أبدًا، فاحسب حساب رحلة كاملة في كل واحدة.

  • حدّ نفسك. لا تقفز إلى سقف فئتك لحظة توفّره — اترك هامشًا لحركة التاجر.

  • اضبط تنبيهات Telegram عندك على 429 rate_limited و 5xx وفشل تسليم webhook.

  • تدقيق ربع سنوي: دوّر مفاتيح المتاجر التي لا تديرها بعد. استعمل DELETE /v1/keys/{key_id}.

مرجع

هل أجاب هذا عن سؤالك؟