للمطورين

ربط نظام الكاشير بدوري

بدل أن يضغط الكاشير «جاهز» في شاشة دوري، يستدعي برنامجك الواجهة بنفسه: ينشئ رقم الطلب عند إصدار الفاتورة، ويرسل «جاهز» عند انتهاء التحضير، فيرنّ جوال الزبون. وإن أردتَ أن يعرف نظامك بكل ما يحدث في دوري، يستقبل أحداثًا موقّعة على رابط يخصّك (Webhook).

١. الفكرة في ثلاث خطوات

  1. الفاتورة في نظامك: يرسل برنامجك POST /api/v1/orders فيرجع رقم الطلب number ورابط claim_url.
  2. الزبون يمسح الرمز: يعرض نظامك (أو شاشة الآيباد) رمز QR لهذا الرابط، فيمسحه الزبون وترتبط الصفحة بجواله وبهذا الرقم.
  3. الجاهزية: عند الانتهاء يرسل برنامجك POST /api/v1/orders/{id}/ready فيرنّ جوال الزبون.

لا حاجة لأي تطبيق على جوال الزبون. ولا يتغيّر شيء على بقية المحل: الكاشير ما زال يستطيع العمل من شاشة دوري إذا تعطّل الربط.

٢. كيف أنشئ مفتاح الربط من لوحة التحكم

المفتاح هو «كلمة السر» التي يستخدمها برنامج الكاشير ليثبت أنه مصرَّح له بالتحدث باسم محلك. تنشئه بنفسك دون مبرمج.

  1. سجّل الدخول إلى لوحة التحكم بحساب صاحب المحل. (الموظفون لا يرون هذا التبويب.)
  2. افتح تبويب «الربط» من الشريط العلوي.
  3. في بطاقة «مفاتيح API لنظام الكاشير (POS)» اكتب في خانة الاسم ما يدلّك عليه، مثل «الكاشير الرئيسي».
  4. اضغط «إنشاء مفتاح».
  5. يظهر مربع أخضر فيه المفتاح، يبدأ بـ dwr_. اضغط «نسخ» وضعه في إعدادات برنامج الكاشير. لا يظهر مرة ثانية. دوري تحفظ بصمة مشفّرة فقط ولا تستطيع استرجاعه. إن ضاع، ألغِه وأنشئ غيره.
  6. يمكنك إنشاء حتى 5 مفاتيح (مثلًا مفتاح لكل فرع أو لكل نظام). ويظهر لكل مفتاح اسمه وأول حروفه وآخر استخدام، وزر «إلغاء المفتاح» يوقفه فورًا.
تنبيه أمني: من يملك المفتاح يستطيع إنشاء الطلبات وتغيير حالتها في محلك. لا ترسله في واتساب ولا تضعه في كود يصل للمتصفح أو لتطبيق الجوال. ضعه في خادم الكاشير فقط.

رابط الـ Webhook (اختياري)

  1. في تبويب «الربط» نفسه، بطاقة «Webhook: إشعار نظامك بالأحداث».
  2. اكتب في «رابط الاستقبال (https فقط)» عنوان نقطة في نظامك، مثل https://pos.example.com/dawri-webhook، ثم «حفظ الرابط».
  3. اضغط «إظهار السر» لتنسخ السر whsec_… الذي تتحقق به من التوقيع. («تدوير السر» يغيّره ويوقف السر القديم.)
  4. اضغط «إرسال اختبار»: يصل حدث ping إلى نظامك، وتظهر النتيجة في «آخر المحاولات».

لتشغيل «رقم الفاتورة» اليدوي بدون API: تبويب «الكاشير والشاشة» ← «طريقة عمل الكاشير» ← «رقم الكاشير (فاتورة نظام نقاط البيع)».

٣. الأسماء كاملة (انسخها كما هي)

العنصرالقيمة
العنوان الأساسي BASE_URLhttps://اسم-نطاقك (نفس عنوان موقع دوري الذي تفتحه، بلا شرطة في آخره)
ترويسة التوثيقAuthorization: Bearer dwr_XXXXXXXXXXXXXXXXXXXXXXXX
نوع المحتوىContent-Type: application/json
متغير مقترح في برنامجك للمفتاحDAWRI_API_KEY
متغير مقترح للعنوانDAWRI_BASE_URL
متغير مقترح لسر الـ WebhookDAWRI_WEBHOOK_SECRET (قيمته whsec_…)
حقول إنشاء طلبref · name · show_on_tablet
حقول الردorder · claim_url · claim_expires_at · on_tablet · duplicate
أسماء الإجراءات (آخر المسار)accept · preparing · ready · collected · cancel · reject
ترويسات الـ WebhookX-Dawri-Event · X-Dawri-Signature
أحداث الـ Webhookorder.created · order.accepted · order.ready · order.collected · order.cancelled · order.rejected · order.reopened · ping

٤. التدفق الكامل (مثال يمكنك نسخه)

أ. التأكد من أن المفتاح يعمل:

curl https://YOUR-DOMAIN/api/v1/me \ -H "Authorization: Bearer dwr_XXXX" # → { "shop": "my-cafe", "name": "قهوة الصباح", "mode": "cafe" }

ب. إنشاء الطلب عند إصدار الفاتورة:

curl -X POST https://YOUR-DOMAIN/api/v1/orders \ -H "Authorization: Bearer dwr_XXXX" \ -H "Content-Type: application/json" \ -d '{"ref":"POS-1001","name":"سعد"}' # 201 → { "order": { "id": 57, "number": 24, "status": "preparing", "name": "سعد", "ref": "POS-1001", "source": "api", "cancel_reason": null, "created_at": 1789000000000, "ready_at": null, "collected_at": null, "phone_connected": false }, "on_tablet": true, "claim_url": "https://YOUR-DOMAIN/c/my-cafe?claim=Kq3xT9...", "claim_expires_at": 1789007200000 }

احتفظ بـ order.id (هنا 57) فهو الذي تستخدمه في بقية الاستدعاءات، وأظهر order.number للزبون على الفاتورة. وclaim_url هو ما يمسحه الزبون (انظر القسم ١١).

ج. عند انتهاء التحضير:

curl -X POST https://YOUR-DOMAIN/api/v1/orders/57/ready \ -H "Authorization: Bearer dwr_XXXX" # 200 → { "order": { "id": 57, "number": 24, "status": "ready", ... } }

د. عند تسليم الطلب (اختياري): POST /api/v1/orders/57/collected.

٥. مسارات API

كل المسارات تحت /api/v1 وتتطلب ترويسة Authorization. الطلبات والردود بصيغة JSON.

المسارالغرضالجسم / الرد
GET /api/v1/meاختبار المفتاح ومعرفة المحلالرد: {shop, name, mode}
POST /api/v1/ordersإنشاء طلب جديد برقم جديدالجسم (كله اختياري):
ref نص حتى 60 حرفًا، رقم فاتورتك
name حتى 30 حرفًا، اسم الزبون
show_on_tablet false لمنع ظهوره على آيباد الزبون
الرد 201: order · claim_url · claim_expires_at · on_tablet
GET /api/v1/ordersقائمة الطلبات الحالية (المستلمة حديثًا)اختياري ?status=ready. الرد: {orders:[{id, number, status, name, ref, source}]}
GET /api/v1/orders/{id}تفاصيل طلبالرد: {order}
POST /api/v1/orders/{id}/acceptقبول طلب معلّق (يصير «قيد التحضير»)الرد: {order}
POST /api/v1/orders/{id}/preparingإعادة طلب جاهز إلى «قيد التحضير»الرد: {order}
POST /api/v1/orders/{id}/readyالطلب جاهز: يرنّ جوال الزبونالرد: {order}
POST /api/v1/orders/{id}/collectedالزبون استلم طلبهالرد: {order}
POST /api/v1/orders/{id}/cancelإلغاء الطلبالجسم اختياري: {"reason":"fake"} أو "left" أو "other"
POST /api/v1/orders/{id}/rejectرفض طلب معلّقالرد: {order}

تكرار الإرسال آمن: إذا أرسل نظامك الطلب نفسه مرتين بنفس ref في اليوم نفسه وهو ما زال فعّالًا، يعيد دوري الطلب نفسه مع "duplicate": true ولا يصدر رقمًا جديدًا. استخدم رقم فاتورتك دائمًا في ref حتى لا تتكرر الأرقام عند إعادة المحاولة بعد انقطاع الشبكة.

٦. كائن الطلب (order)

الحقلالنوعالمعنى
idرقممعرّف الطلب داخل دوري. استخدمه في المسارات.
numberرقم 1–999الرقم الذي يراه الزبون ويُنادى به. يبدأ من 1 كل يوم (أو متسلسلًا حسب إعداد المحل).
statusنصpending · preparing · ready · collected · cancelled · rejected
nameنص أو nullاسم الزبون إن أُرسل.
refنص أو nullرقم فاتورتك الذي أرسلته.
sourceنصapi أو manual (من الكاشير) أو qr (مسح الزبون بنفسه).
cancel_reasonنص أو nullfake · left · other · rejected
created_at · ready_at · collected_atرقم أو nullوقت الحدث بالميلي ثانية (Unix epoch).
phone_connectedtrue/falseهل صفحة الزبون مفتوحة الآن أو سمح بالإشعارات. تصير true بعد أن يمسح الزبون ويبقي الصفحة مفتوحة، ولا تعني أنه مسح فقط.

٧. الحالات والانتقالات المسموحة

منإلى (الإجراء)
pendingpreparing (accept) · rejected (reject) · cancelled (cancel)
preparingready (ready) · cancelled (cancel)
readycollected (collected) · preparing (preparing) · cancelled (cancel)
collectedready (ready: لإعادة النداء)

الطلبات التي تنشئها عبر API تبدأ مباشرة بحالة preparing. وأي انتقال غير مسموح يرجع 409.

٨. الأخطاء

الرمزerrorالسبب والحل
401invalid_api_keyالمفتاح ناقص أو خاطئ أو أُلغي، أو الترويسة ليست بصيغة Bearer dwr_….
403inactiveاشتراك المحل منتهٍ أو موقوف. جدّد الاشتراك من لوحة التحكم.
404not_found · unknown_actionالطلب ليس لمحلك، أو اسم الإجراء غير صحيح.
409invalid_transitionانتقال غير مسموح (مثلًا «جاهز» لطلب ملغى). يحتوي الرد على الحالة الحالية في status.
429rate_limitedتجاوزتَ 120 طلبًا في الدقيقة لهذا المفتاح. انتظر دقيقة.

شكل الخطأ: {"error":"invalid_transition","status":"collected"}. بعض الحالات تُرجع حقولًا إضافية مثل status.

٩. Webhook: دوري تخبر نظامك

إذا حفظتَ رابط الاستقبال، ترسل دوري POST بصيغة JSON إلى رابطك عند كل حدث. مفيد إذا ضغط الكاشير «جاهز» من شاشة دوري ويريد نظامك أن يعرف، أو لتسجيل الطلبات التي أنشأها الزبائن بأنفسهم.

POST https://pos.example.com/dawri-webhook Content-Type: application/json X-Dawri-Event: order.ready X-Dawri-Signature: t=1789000300,v1=9f2c...e7a1 { "id": "evt_3fa91c0b7d2e4a11", "type": "order.ready", "created": 1789000300, "shop": "my-cafe", "data": { "id": 57, "number": 24, "status": "ready", "name": "سعد", "ref": "POS-1001", "source": "api", "cancel_reason": null, "created_at": 1789000000000, "ready_at": 1789000300000, "collected_at": null, "phone_connected": true } }
الحدث typeمتى يصل
order.createdأُنشئ طلب من شاشة الكاشير أو بمسح الزبون. (الطلبات التي أنشأها نظامك عبر API لا ترسل هذا الحدث، لأنك تعرفها.)
order.acceptedانتقل الطلب إلى «قيد التحضير».
order.readyالطلب جاهز (رنّ جوال الزبون).
order.collectedتم تسليم الطلب.
order.cancelledأُلغي الطلب. السبب في cancel_reason.
order.rejectedرُفض طلب معلّق.
order.reopenedأُعيد طلب جاهز إلى «قيد التحضير».
pingزر «إرسال اختبار». الحقل data فيه {"message":"Hello from Dawri"}.

١٠. التحقق من التوقيع

تأكد دائمًا أن الطلب من دوري قبل أن تصدّقه. الترويسة X-Dawri-Signature بصيغة t=<unix>,v1=<hex>، والتوقيع هو HMAC-SHA256 بالسر whsec_… على النص: t + نقطة + الجسم الخام كما وصل (قبل أي تحويل JSON).

Node.js:

const crypto = require('node:crypto'); function verify(rawBody, header, secret) { const parts = Object.fromEntries(header.split(',').map((p) => p.split('='))); const expected = crypto.createHmac('sha256', secret) .update(parts.t + '.' + rawBody).digest('hex'); const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1)); const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // ارفض الأقدم من 5 دقائق return ok && fresh; } // Express: استخدم express.raw({ type: 'application/json' }) لتحصل على rawBody app.post('/dawri-webhook', express.raw({ type: 'application/json' }), (req, res) => { if (!verify(req.body.toString('utf8'), req.get('X-Dawri-Signature') || '', process.env.DAWRI_WEBHOOK_SECRET)) return res.sendStatus(401); const evt = JSON.parse(req.body); // evt.type, evt.data.number, evt.data.ref ... res.sendStatus(200); });

Python:

import hmac, hashlib, time def verify(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) signed = parts["t"].encode() + b"." + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts["v1"]) and abs(time.time() - int(parts["t"])) < 300

PHP:

function verify(string $raw, string $header, string $secret): bool { parse_str(str_replace(',', '&', $header), $p); // t=...&v1=... $expected = hash_hmac('sha256', $p['t'] . '.' . $raw, $secret); return hash_equals($expected, $p['v1']) && abs(time() - (int)$p['t']) < 300; } $raw = file_get_contents('php://input'); if (!verify($raw, $_SERVER['HTTP_X_DAWRI_SIGNATURE'] ?? '', getenv('DAWRI_WEBHOOK_SECRET'))) { http_response_code(401); exit; }

١١. كيف يظهر رمز QR للزبون

لديك ثلاث طرق، وكلها تستخدم claim_url الذي رجع من إنشاء الطلب:

الرابط صالح ساعتين (claim_expires_at) ولجوال واحد فقط. من يصوّره ويرسله لغيره لن يعمل عنده، ولا يتكرر.

١٢. الحدود والملاحظات

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