لشركات البرمجيات ونقاط البيع

واجهة الشركاء

نظامك يخدم تجارًا كثرًا — تكاملٌ واحد معنا يعطي كل واحد منهم منشأة فوترة كاملة لدى الهيئة: شهادته الخاصة، سلسلته الخاصة، وفواتير بلا حدّ. أنت تنشئ الحسابات برمجيًا، ونحن نتولى تعقيدات ZATCA.

BASE URL https://zatcatools.com/api/partner
Building the integration? The full English API Reference — every endpoint, parameter and error code — lives at /docs/partner-api. API Reference →

كيف تعمل

الهيئة تُصدر الشهادات لكل مكلَّف على حدة — لا وجود لشهادة مشتركة يُوقَّع بها عن الجميع. لذلك كل تاجر تنشئه عبر هذه الواجهة هو منشأة مستقلة كاملة: رقمه الضريبي، شهادته، سلسلة فواتيره (ICV/PIH)، وأرشيفه. ما توفره الواجهة هو أنك تنشئ هذه المنشآت وتديرها من نظامك بدل أن يسجّل كل تاجر بنفسه.

الخطوة من يقوم بها كيف
1 · إنشاء منشأة التاجرنظامكPOST /merchants
2 · الربط مع بوابة فاتورةنظامك — والتاجر يجلب الرمزPOST /merchants/{id}/connect
3 · إصدار الفواتيرنظامكPOST /api/v1/invoices + X-Merchant-Id

الاشتراكات: اتفاقك معنا يحدد عدد اشتراكات التجار المدفوعة. كل تاجر نشط يستهلك اشتراكًا، وإيقاف اشتراك تاجرٍ غادَرَ يُعيده لرصيدك. حين يُستهلك الرصيد ترجع الواجهة 402 seat_limit_reached — تواصل معنا لزيادتها.

حقيقة رمز فاتورة — اقرأها قبل أن تكتب سطرًا

خطوة واحدة في الرحلة كلها لا يمكن لأي واجهة برمجية أن تتولاها عن التاجر: رمز التحقق (OTP) من بوابة فاتورة. الهيئة تُصدره للمكلَّف نفسه داخل بوابتها fatoora.zatca.gov.sa، ولا يصل إلينا ولا إليك. هذا قيد من الهيئة على السوق كله، لا قيد عندنا.

القيد على مصدر الرمز فقط — أما إدخاله فيحدث داخل شاشتك أنت: اعرض لتاجرك «أدخل رمز التحقق من بوابة فاتورة»، ومرّر ما يكتبه إلى POST /merchants/{id}/connect وتابع التقدم من GET /merchants/{id}/status — الشهادة وفحوص التوافق الستة خلال دقيقتين تقريبًا، وتاجرك لم يغادر نظامك. والقاعدة نفسها تنطبق على كل جهاز فرع لاحقًا: رمز جديد لكل جهاز (إلحاق حل فوترة جديد ← عدد الأجهزة) — تفاصيلها في فروع التاجر وأجهزته.

لا تريد بناء الشاشة؟ onboarding_url يبقى بديلًا جاهزًا: رابط موقَّع يفتح صفحتنا على الخطوة نفسها.

الاستيثاق

مفتاح الشريك يبدأ بـztkp_live_ وتستلمه منا عند توقيع الاتفاق — يظهر مرة واحدة، فخزّنه في متغير بيئة. وهو للأنظمة فقط: بوابة الشركاء تفتح ببريدك المسجّل ورمز تحقق، فلا يحتاج مفتاحك أن يمر بحافظة أحد. وهو مفتاحك الوحيد: يدير حساباتك هنا، ويُصدر فواتير كل تجّارك على واجهة الفواتير — التاجر المقصود تسمّيه في كل نداء بترويسة X-Merchant-Id. سرّ واحد في خزنتك، وتدوير واحد تديره.

curl https://zatcatools.com/api/partner/account \
  -H "Authorization: Bearer ztkp_live_..."

الأخطاء

HTTP code المعنى
401 unauthenticated مفتاح الشريك مفقود أو غير صحيح — أو أرسلت مفتاح تاجر بالخطأ
402 seat_limit_reached كل اشتراكاتك المدفوعة مشغولة — حرّر اشتراكًا أو تواصل لزيادتها
403 partner_suspended حساب الشراكة موقوف — تواصل معنا
404 not_found لا تاجر بهذا المعرّف ضمن شراكتك
409 email_taken / vat_taken البريد له حساب قائم، أو الرقم الضريبي مسجّل لمنشأة أخرى
422 validation_failed بيانات ناقصة أو غير صالحة — الرسالة توضح السبب
429 too_many_requests تجاوزت حد الطلبات (120/دقيقة)

حسابك واشتراكاتك

GET /api/partner/account

{
  "partner": {
    "name": "Smart POS Co.",
    "status": "active",
    "seats": { "limit": 50, "used": 34, "remaining": 16 }
  }
}

إنشاء تاجر

POST /api/partner/merchants

يستهلك اشتراكًا وينشئ منشأة كاملة بفوترة بلا حدّ. name وemail إلزاميان؛ vat_number إلزامي أيضًا — هو هوية المنشأة لدى الهيئة، ونظامك يعرفه عن عميله دائمًا. السجل التجاري اختياري. وللعنوان أرسل short_address — العنوان الوطني المختصر (4 أحرف و4 أرقام، مثل RRRD2929): نحلّ منه العنوان التفصيلي كاملًا تلقائيًا كما في التسجيل العادي، وإن تعذّر الحلّ يكمل التاجر بياناته في خطوة الـOnboarding — لا شيء يتعطل. البريد يجب أن يكون جديدًا عندنا: بريد له حساب قائم يُرفض بـ409 ولا يُتبنّى — معرفتك ببريد تاجر ليست بابًا إلى حساب يملكه.

curl -X POST https://zatcatools.com/api/partner/merchants \
  -H "Authorization: Bearer ztkp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "مطعم الذواقة — فرع المروج",
    "email": "[email protected]",
    "vat_number": "310000000000003",
    "cr_number": "1010203040",
    "short_address": "RRRD2929"
  }'
HTTP/1.1 201 Created
{
  "merchant": { "id": 34, "name": "…", "status": "awaiting_onboarding", "seat": "active" },
  "onboarding_url": "https://…",     // موقَّع، صالح 72 ساعة
  "seats": { "limit": 50, "used": 35, "remaining": 15 }
}

لا مفتاح لكل تاجر — خزّن merchant.id مقابل عميلك في نظامك: هو ما يذهب في ترويسة X-Merchant-Id مع مفتاح شراكتك في كل نداء فوترة.

ربط التاجر — من داخل شاشتك

POST /api/partner/merchants/{id}/connect · GET /api/partner/merchants/{id}/status

قبل النداء تأكد أن بيانات المنشأة مكتملة — name و vat_number و cr_number و short_address — أكملها بـPATCH /merchants/{id} إن نقصت. النقص يُرفض قبل صرف الرمز: رمز فاتورة يُستخدم مرة واحدة، وحرقه على نقص حقل هو أسوأ نسخ هذا الخطأ.

# The merchant typed the six digits into YOUR screen:
curl -X POST https://zatcatools.com/api/partner/merchants/34/connect \
  -H "Authorization: Bearer ztkp_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "otp": "123456" }'

HTTP/1.1 202 Accepted
{ "status": "connecting", "poll": "…/merchants/34/status" }
# Poll every couple of seconds while your checklist spins:
GET /api/partner/merchants/34/status
{
  "connected": false, "connecting": true, "stalled": false,
  "compliance_passed": 4,        // of the 6 checks
  "error": null
}
# …until:
{ "connected": true, "compliance_passed": 6, "certificate_expires_at": "2029-08-31" }

error يهمك منه اثنان: otp_invalid — الرمز خاطئ أو منتهٍ، اطلب من تاجرك رمزًا جديدًا؛ و stalled: true — توقف العدّ، أعد المحاولة برمز جديد. وبعد الربط تُقفل حقول الهوية (الاسم والرقم الضريبي والسجل): الشهادة الصادرة تحملها، وتغييرها يجعل الوثائق تخالف شهادتها.

قائمة التجار وتفاصيلهم

GET /api/partner/merchants · GET /api/partner/merchants/{id}

الحقل الذي تبني عليه شاشتك هو status:

awaiting_onboarding أُنشئ ولم يُكمل الربط — اعرض له رابط الإكمال
connected مرتبط بالهيئة — فواتيره تصدر الآن
failed آخر محاولة ربط فشلت — اطلب رابطًا جديدًا وأعد توجيهه
trial يجرّب على بيئة الهيئة التجريبية
released حرّرتَ اشتراكه — بياناته محفوظة ولا فوترة جديدة

إصدار الفواتير

من هنا وصاعدًا تستخدم واجهة الفواتير v1 بمفتاح شراكتك نفسه + ترويسة تسمّي التاجر — إصدار، إشعارات دائن ومدين، XML وPDF وQR. مثال بيع من نقطة بيعك:

curl -X POST https://zatcatools.com/api/v1/invoices \
  -H "Authorization: Bearer ztkp_live_…" \
  -H "X-Merchant-Id: 34"                  # التاجر صاحب هذا البيع \
  -H "Content-Type: application/json" \
  -d '{
    "type": "simplified",
    "branch_id": 3,                  // البيع من فرع؟ سمِّ الفرع وجهازه معًا —
    "device_id": 7,                  // القاعدة نفسها للضريبية والمبسطة سواء
    "lines": [
      { "name": "وجبة غداء", "quantity": 2, "unit_price": 45.00 },
      { "name": "دواء", "quantity": 1, "unit_price": 120.00,
        "tax_category": "Z", "tax_reason_code": "VATEX-SA-35" }
    ]
  }'

البنود تقبل tax_category (S افتراضيًا · Z صفرية · E معفاة · O خارج النطاق) — والفئات غير الخاضعة تتطلب tax_reason_code من قائمة الهيئة، والفاتورة الواحدة تجمع فئات مختلفة بلا مشكلة. والبيع من فرع يسمّي branch_id وdevice_id معًا — للضريبية والمبسطة سواء؛ التفاصيل في فروع التاجر وأجهزته أدناه.

فروع التاجر وأجهزته

تاجرك له أكثر من نقطة بيع؟ كل الفروع تحت رقمه الضريبي، لكن كل فرع يُصدر عبر جهازه الخاص (EGS unit) — نموذج الهيئة نفسه: التاجر يولّد من بوابة فاتورة رمز OTP لكل جهاز (إلحاق حل فوترة جديد ← عدد الأجهزة)، أنت تجمعه في شاشتك وتمرره لنا، وكل جهاز يخرج بشهادته وسلسلة فواتيره المستقلة. ربط المنشأة الأساسي (أعلاه) هو جهاز المقر الرئيسي؛ ما يلي للفروع. نفس مفتاحك + ترويسة التاجر:

# 1. الفرع
curl -X POST https://zatcatools.com/api/v1/branches \
  -H "Authorization: Bearer ztkp_live_…" -H "X-Merchant-Id: 34" \
  -d '{ "name": "فرع النخيل مول", "cr_number": "1010999888", "short_address": "RESB3139" }'
# → 201 { "branch": { "id": 3, … } }

# 2. جهاز الفرع — otp جمعته من شاشة تاجرك
curl -X POST https://zatcatools.com/api/v1/branches/3/devices \
  -H "Authorization: Bearer ztkp_live_…" -H "X-Merchant-Id: 34" \
  -d '{ "otp": "482913", "name": "كاشير 1" }'
# → 202 { "device": { "id": 7, "status": "pending", … } }

# 3. تتبّع حتى connected (عادة أقل من دقيقة)
curl https://zatcatools.com/api/v1/devices/7 \
  -H "Authorization: Bearer ztkp_live_…" -H "X-Merchant-Id: 34"
# → { "device": { "status": "connected", "usable": true, … } }

# 4. بيع من الفرع — الفاتورة تسمّي فرعها وجهازها معًا
curl -X POST https://zatcatools.com/api/v1/invoices \
  -H "Authorization: Bearer ztkp_live_…" -H "X-Merchant-Id: 34" \
  -d '{ "type": "simplified", "branch_id": 3, "device_id": 7,
        "lines": [{ "name": "وجبة", "quantity": 1, "unit_price": 45 }] }'

الفرع الواحد يتحمّل عدة أجهزة — كرر الخطوة 2 برمز OTP جديد لكل جهاز، وكلٌّ بسلسلته المستقلة فتُصدر نقاط البيع بالتوازي. فاتورة الفرع تسمّي جهازها: خزّن device_id عند كل نقطة بيع ومرّره مع branch_id على كل فاتورة — أحدهما بلا الآخر يُرفض (device_required / branch_required)، وجهاز غير متصل يُرفض بـ device_not_usable. الإشعارات تتبع فرع فاتورتها الأصل تلقائيًا وجهازها عليها اختياري. فشل التفعيل بـ otp_invalid؟ الرمز خُطئ أو انتهت ساعته أو استُهلك — اطلب من تاجرك رمزًا جديدًا وأعد التفعيل على نفس الجهاز: POST /v1/devices/{id}/connect. القائمة الكاملة للفروع وأجهزتها دائمًا على GET /v1/account.

إيقاف اشتراك وإعادته

POST /api/partner/merchants/{id}/release · POST /api/partner/merchants/{id}/reinstate

تاجر غادر نظامك؟ أوقف اشتراكه ليخدم غيره. الإيقاف لا يحذف شيئًا أبدًا — الهيئة تُلزم بأرشيف ست سنوات، وسلسلة الفواتير لا تنقطع: المنشأة ووثائقها ودخول صاحبها كلها تبقى، وقراءة أرشيفه عبر الـ API تبقى أيضًا (كل نداءات GET تعمل للموقوف — تصدير وتدقيق بلا انقطاع)؛ الذي يتوقف هو الفوترة الجديدة وكل فعل يُنشئ شيئًا. عاد التاجر؟ reinstate يعيد تفعيله على اشتراك شاغر ويستأنف سلسلته من حيث توقفت.

حدود الاستخدام

واجهة الشركاء: 120 طلبًا في الدقيقة. واجهة الفواتير: 60 طلبًا في الدقيقة على مفتاحك — يكفي مئات نقاط البيع في الاستخدام الواقعي؛ ولو بلغت حجمًا يتجاوزه فراسلنا ونرفعه لك.

جاهز تبدأ؟ راسلنا على [email protected] — نتفق على الاشتراكات، وتستلم مفتاحك، وأول تاجر لك يُصدر خلال يوم.