دليل ربط واجهة برمجة التطبيقات (API)

منصة فوترة — دليل المطوّر لتكامل الأنظمة المحاسبية الخارجية

الإصدار 1.1 Base URL: https://v2.fwtrh.com/externalApi JSON

1. نظرة عامة

يوضّح هذا الدليل خطوات ربط نظامكم المحاسبي الخاص بمنصة فوترة عبر واجهة API الخارجية. تغطي هذه الواجهة المسارات اللازمة لإنشاء جهات التعامل (العملاء/الموردين)، ثم إنشاء فواتير المبيعات والمشتريات، ثم إصدار الإشعارات الدائنة (المرتجعات) والإشعارات المدينة، وأخيرًا إرسال الفواتير إلى هيئة الزكاة والضريبة والجمارك (ZATCA).

ترتيب التكامل الموصى به:

  • الخطوة 1: إنشاء حساب على المنصة (يُربط تلقائيًا بـ company_id خاص بالعميل).
  • الخطوة 2: المصادقة والتأكد من نجاح الاتصال.
  • الخطوة 3: جلب البيانات المرجعية (العملات، الضرائب، طرق الدفع).
  • الخطوة 4: إنشاء جهات التعامل (العملاء).
  • الخطوة 5: إنشاء فواتير المبيعات (مع إمكانية التأكيد والإرسال لزاتكا).
  • الخطوة 6: إصدار الإشعارات الدائنة والمدينة عند الحاجة.
  • الخطوة 7: متابعة حالة الإرسال إلى زاتكا.
هذا الدليل كافٍ للتكامل: يغطي كل مسار في الواجهة الخارجية: الترويسات، المعاملات، جسم الطلب، أمثلة cURL، وشكل الاستجابة. لا حاجة لفتح Postman إذا اتبعت الأقسام بالترتيب ثم نسخت سكربت التدفّق في القسم 13.

2. الحساب و company_id

ينشئ العميل حسابًا عاديًا على المنصة كأي مستخدم. بمجرد إنشاء الحساب يُنشأ له سجل شركة خاص به ويحصل على company_id فريد.

عزل البيانات (Multi-tenancy): كل العمليات عبر الـ API تتم تلقائيًا في نطاق شركة المستخدم المُصادَق به فقط. لا حاجة لإرسال company_id يدويًا في الطلبات — يستنتجه النظام من بيانات الاعتماد، ولا يمكن للحساب الوصول إلى بيانات أي شركة أخرى.

عمليًا هذا يعني:

  • جميع جهات التعامل والفواتير التي يُنشئها العميل تُنسب تلقائيًا إلى شركته.
  • أي معرّفات يستخدمها (contact_id, currency_id, tax_id, branch_id) يجب أن تخص شركته، وإلا تُرفض العملية.

3. المصادقة (Authentication)

تستخدم الواجهة الخارجية مصادقة عبر ترويسات HTTP مخصّصة. يجب إرسال بيانات اعتماد المستخدم (البريد الإلكتروني وكلمة المرور) في كل طلب عبر الترويستين التاليتين:

الترويسة (Header)الوصف
X-Auth-Usernameالبريد الإلكتروني للمستخدم في المنصة
X-Auth-Passwordكلمة مرور المستخدم
Acceptapplication/json
Content-Typeapplication/json
  • في حال عدم إرسال الترويستين تُعاد 401 مع الرسالة Credentials not provided.
  • في حال كانت البيانات غير صحيحة تُعاد 401 مع الرسالة Invalid credentials.
  • يُربط المستخدم تلقائيًا بشركته، وكل العمليات تتم في نطاق هذه الشركة فقط.

مثال (cURL)

curl -X GET "https://v2.fwtrh.com/externalApi/contacts" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"

لا يوجد توكن أو جلسة دائمة. أرسل الترويستين في كل طلب. لا تستخدم Authorization: Basic إلا إذا أضفت الترويستين أعلاه أيضًا.

4. اصطلاحات الواجهة

البندالقاعدة
العنوان الأساسيhttps://v2.fwtrh.com/externalApi
التنسيقJSON — Accept: application/json و Content-Type: application/json في طلبات POST/PUT
التواريخYYYY-MM-DD (مثال: 2026-09-26)
المعرّفاتأرقام صحيحة تخص شركة المستخدم فقط. أي id من شركة أخرى يُرفض بـ 422 أو 403.
الترقيمالقوائم تُرجَع مُرقَّمة افتراضيًا. استخدم perPage لعدد العناصر، أو nopaginate=1 لإرجاع كل النتائج دفعة واحدة.
نجاح الإنشاءاستجابة الإنشاء عادة { "status": true, ... }. تحقّق من status قبل المتابعة.

شكل قائمة مُرقَّمة (Laravel)

{
  "data": [ { "id": 1 } ],
  "links": {
    "first": "https://v2.fwtrh.com/externalApi/contacts?page=1",
    "last": "https://v2.fwtrh.com/externalApi/contacts?page=3",
    "prev": null,
    "next": "https://v2.fwtrh.com/externalApi/contacts?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 3,
    "per_page": 15,
    "to": 15,
    "total": 42
  }
}
لا يوجد مسار تأكيد لاحق للفواتير: الواجهة الخارجية لا تدعم PUT/confirm بعد الإنشاء. إذا أردت فاتورة مؤكدة ومُرسلة لزاتكا، أرسل confirm=true (أو set_as_paid=true) في طلب الإنشاء نفسه. المسودة تُحفظ فقط، ولا يمكن تأكيدها عبر هذه الواجهة لاحقًا.

5. فهرس المسارات

كل المسارات المتاحة تحت /externalApi. التفاصيل والأمثلة في الأقسام التالية.

المجموعةالطريقة والمسارالغرض
مرجعيGET/currenciesقائمة العملات
مرجعيGET/taxesقائمة الضرائب
مرجعيGET/discountsقائمة الخصومات
مرجعيPOST/discountsإنشاء خصم
مرجعيPUT/discounts/{id}تعديل خصم
مرجعيDELETE/discounts/{id}حذف خصم
مرجعيGET/paymentMethodsطرق الدفع
مرجعيGET/countriesالدول
مرجعيGET/citiesالمدن
مرجعيGET/branches/userفروع المستخدم
عملاءGET/contactsبحث/قائمة جهات التعامل
عملاءPOST/contactsإنشاء جهة تعامل
عملاءGET/contacts/{id}عرض جهة تعامل
عملاءPUT/contacts/{id}تعديل جهة تعامل
عملاءDELETE/contacts/{id}حذف جهة تعامل
فواتيرGET/invoicesقائمة الفواتير
فواتيرPOST/invoicesإنشاء فاتورة
فواتيرGET/invoices/{id}عرض فاتورة
فواتيرPOST/invoices/{id}/zatcaإرسال فاتورة مؤكدة لزاتكا
إشعاراتPOST/invoices/{id}/fullReverseمرتجع كامل
إشعاراتPOST/invoices/{id}/partialReverseمرتجع جزئي
إشعاراتPOST/invoices/{id}/debitإشعار مدين

6. البيانات المرجعية (Lookups)

قبل إنشاء الفواتير، احصل على المعرّفات (IDs) المطلوبة من نقاط النهاية التالية. خزّن هذه المعرّفات في نظامكم (أو اجلبها مرة عند الإقلاع) لأن الفواتير تعتمد عليها.

الغرضالطريقة والمسار
العملاتGET/externalApi/currencies
الضرائبGET/externalApi/taxes
الخصوماتGET/externalApi/discounts
طرق الدفعGET/externalApi/paymentMethods
الدولGET/externalApi/countries
المدنGET/externalApi/cities
الفروع (حسب المستخدم)GET/externalApi/branches/user
تنبيه حول الضرائب:
  • لكل ضريبة حقل tax_type وقيمته sale (للمبيعات) أو purchase (للمشتريات)، ويجب أن تتطابق مع نوع الفاتورة.
  • لكل ضريبة حقل amount_type وقيمته percent (نسبة) أو fixed (قيمة ثابتة).
  • لتصفية ضرائب المبيعات فقط: GET /externalApi/taxes?type=S (أي قيمة أخرى تُرجع ضرائب المشتريات).

أمثلة الجلب

# العملات
curl -X GET "https://v2.fwtrh.com/externalApi/currencies?nopaginate=1" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"

# ضرائب المبيعات فقط
curl -X GET "https://v2.fwtrh.com/externalApi/taxes?type=S&nopaginate=1" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"

# المدن التابعة لدولة معيّنة (Saudi Arabia عادة country=1)
curl -X GET "https://v2.fwtrh.com/externalApi/cities?country=1&nopaginate=1" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"

# طرق الدفع + الخصومات + الفروع
curl -X GET "https://v2.fwtrh.com/externalApi/paymentMethods?nopaginate=1" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"

شكل الاستجابة (مختصر)

// GET /taxes?type=S
[
  {
    "id": 5,
    "name": { "Ar": "ضريبة القيمة المضافة", "En": "VAT" },
    "amount": 15,
    "amount_type": "percent",
    "tax_type": "sale",
    "tax_code": "S",
    "type": "excluded"
  }
]

// GET /currencies
[ { "id": 1, "name": "Saudi Riyal", "code": "SAR", "symbol": "﷼" } ]

// GET /paymentMethods
[ { "id": 1, "name": { "Ar": "نقداً", "En": "Cash" } } ]

// GET /discounts
[ { "id": 12, "name": { "Ar": "خصم 10%", "En": "10% off" }, "type": "percent", "amount": 10 } ]

إنشاء خصم جديد

إذا لم يكن الخصم موجودًا مسبقًا في الحساب، يمكن إنشاؤه ثم استخدام id الناتج داخل بنود الفاتورة.

curl -X POST "https://v2.fwtrh.com/externalApi/discounts" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ "name": "10% off", "type": "percent", "amount": 10 }'

# تعديل: PUT /externalApi/discounts/{id}
# حذف:  DELETE /externalApi/discounts/{id}
الحقلإلزامي؟الوصف
nameنعماسم الخصم
typeنعمfixed / percent
amountنعمالقيمة (مبلغ ثابت أو نسبة)

7. جهات التعامل (Contacts)

جهة التعامل هي العميل أو المورد. أنشئ جهة التعامل أولًا للحصول على contact_id لاستخدامه في الفواتير.

7.1 إنشاء جهة تعامل

POST/externalApi/contacts

الحقلإلزامي؟الوصف
typeنعمcompany / individual
nameنعماسم جهة التعامل
account_typeلاtaxable / untaxable
emailلابريد إلكتروني صحيح
phoneلارقم الهاتف
secondary_phoneلارقم هاتف ثانوي
vatلاالرقم الضريبي (15 خانة تبدأ وتنتهي بـ 3)
registryلاالسجل التجاري
country_idلامعرّف الدولة
city_idلامعرّف المدينة
plot_identificationلارقم القطعة / تحديد الأرض
building_noلارقم المبنى
streetلاالشارع
districtلاالحي
zipلاالرمز البريدي
currency_idلامعرّف العملة الافتراضية
identitty_typesلانوع الهوية: CRN / NAT / MOM / MLS / SAG / OTH (الاسم كما هو مكتوب)
national_idلارقم الهوية الوطنية
credit_limitلاحد الائتمان (رقم ≥ 0)
branch_idلامعرّف الفرع
parent_idلامعرّف الجهة الأم (إن وُجدت)
ملاحظة زاتكا مهمة: إذا كانت جهة التعامل من نوع taxable فيجب أن يكون vat مكوّنًا من 15 خانة يبدأ وينتهي بالرقم 3، وإلا سيُرفض إنشاء/تأكيد الفاتورة المرتبطة بها. للشركات المرتبطة بشهادة زاتكا يُطلب أيضًا: الشارع، الدولة، المدينة، والرمز البريدي (حسب الدولة).

نوع مستند زاتكا يُحدَّد تلقائيًا من العميل: standard إذا كان taxable برقم ضريبي سعودي صحيح أو من نوع company، وإلا simplified (B2C).

مثال

curl -X POST "https://v2.fwtrh.com/externalApi/contacts" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "type": "company",
    "name": "Client Trading Co.",
    "account_type": "taxable",
    "vat": "300000000000003",
    "email": "[email protected]",
    "phone": "0512345678",
    "country_id": 1,
    "city_id": 10,
    "currency_id": 1
  }'

استجابة الإنشاء

{
  "status": true,
  "contact": {
    "id": 123,
    "type": "company",
    "account_type": "taxable",
    "name": { "Ar": "Client Trading Co.", "En": "Client Trading Co." },
    "email": "[email protected]",
    "phone": "0512345678",
    "vat": "300000000000003",
    "currency_id": 1,
    "country_id": 1,
    "city_id": 10
  }
}

استخدم contact.id كـ contact_id في الفواتير. خزّن الربط مع معرّف العميل في نظامكم.

7.2 باقي عمليات جهات التعامل

العمليةالطريقة والمسار
قائمة جهات التعاملGET/externalApi/contacts
عرض جهة تعاملGET/externalApi/contacts/{id}
تعديل جهة تعاملPUT/externalApi/contacts/{id}
حذف جهة تعاملDELETE/externalApi/contacts/{id}
# عرض
curl -X GET "https://v2.fwtrh.com/externalApi/contacts/123" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"

# تعديل (نفس حقول الإنشاء)
curl -X PUT "https://v2.fwtrh.com/externalApi/contacts/123" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ "name": "Client Trading Co.", "phone": "0511111111", "vat": "300000000000003" }'

# حذف
curl -X DELETE "https://v2.fwtrh.com/externalApi/contacts/123" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"

8. التحقق من وجود العميل قبل إنشاء الفاتورة

عند إنشاء فاتورة عبر التكامل العام (External API)، تربط الفاتورة بالعميل من خلال الحقل contact_id. النظام لا ينشئ العميل تلقائيًا من داخل طلب الفاتورة؛ لذلك تقع على نظامكم مسؤولية التحقق أولًا هل العميل مضاف مسبقًا أم يجب إنشاؤه، ثم تمرير contact_id الناتج.

الآلية الموصى بها (خطوة بخطوة)

  • 1) ابحث عن العميل في فوترة عبر معرّف فريد لديك (يُفضّل الرقم الضريبي vat أو السجل التجاري registry، أو الاسم).
  • 2) إن وُجد: استخدم id العائد كـ contact_id في الفاتورة مباشرة.
  • 3) إن لم يوجد: أنشئ العميل عبر POST /externalApi/contacts، ثم استخدم id الجديد.
  • 4) خزّن الربط (معرّف العميل عندكم ↔ contact_id في فوترة) لتجنّب البحث مستقبلًا ومنع التكرار.

البحث عن العميل

يدعم مسار قائمة جهات التعامل معاملات بحث (query parameters) للتصفية:

المعاملالوصف
vatبحث بالرقم الضريبي (مطابقة تامة — الأنسب لمنع التكرار)
qبحث بالاسم (مطابقة جزئية)
idبحث بمعرّف العميل في فوترة
typeتصفية حسب النوع (company / individual)
account_typeتصفية حسب (taxable / untaxable)
parent_idتصفية حسب الجهة الأم
البحث الدقيق عبر الرقم الضريبي: يدعم المسار مُرشّحًا مباشرًا للرقم الضريبي عبر المعامل vat (مطابقة تامة). هذا هو المعرّف الأفضل لتجنّب التكرار لأنه فريد لكل منشأة خاضعة للضريبة. إن لم يُرجِع البحث أي نتيجة فهذا يعني أن العميل غير موجود ويجب إنشاؤه.

مثال

# 1) ابحث بالرقم الضريبي (الأدق لمنع التكرار)
curl -X GET "https://v2.fwtrh.com/externalApi/contacts?vat=300000000000003" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"

# (بديل) ابحث بالاسم:
#   GET /externalApi/contacts?q=Client%20Trading

# إن كانت النتيجة فارغة → 2) أنشئ العميل (انظر القسم 5) واحصل على id

# 3) استخدم contact_id في الفاتورة
curl -X POST "https://v2.fwtrh.com/externalApi/invoices" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ "type": "out_invoice", "contact_id": 123, "currency_id": 1,
        "lines": [ { "label": "Item", "description": "Item", "price": 100, "qty": 1 } ] }'
ملاحظة (المطابقة التلقائية): في تكاملات معيّنة جاهزة (مثل تكامل Xero) يقوم النظام بمطابقة العميل تلقائيًا بالترتيب: معرّف التكامل الخارجي ثم الرقم الضريبي (vat) ثم السجل التجاري (registry)، وينشئ العميل تلقائيًا عند عدم وجوده. أمّا في التكامل العام (External API) فالتحقق يتم من طرفكم كما هو موضّح أعلاه.
تجنّب التكرار: إنشاء العميل في كل مرة دون بحث مسبق سيؤدي لإنشاء عملاء مكرّرين. اعتمد دائمًا على معرّف فريد (vat / registry) أو ربط مخزّن (contact_id) قبل الإنشاء.

9. فواتير المبيعات (Invoices)

إنشاء فاتورة مبيعات هو المسار الأساسي للتكامل. حدّد النوع، اربط العميل، أرسل البنود، ثم اختر المسودة أو التأكيد أو التسجيل كمدفوعة.

أنواع الفواتير والحالات

typeالمعنىزاتكا؟
out_invoiceفاتورة مبيعات (invoice_type=388)نعم
in_invoiceفاتورة مشترياتلا
out_refundإشعار دائن لمبيعات (invoice_type=381) — يُفضَّل إنشاؤه عبر fullReverse/partialReverseنعم
in_refundإشعار دائن لمشترياتلا
الحقلالقيم
status0 = draft · 1 = confirmed · 2 = cancelled · -1 = void
payment_statusunpaid · partially paid · paid
document_typestandard / simplified — يُحدَّد تلقائيًا من العميل
invoice_type388 فاتورة · 381 إشعار دائن · غير 388 = إشعار

POST/externalApi/invoices

الحقول الأساسية

الحقلإلزامي؟الوصف
typeنعمout_invoice / in_invoice / out_refund / in_refund
currency_idنعممعرّف العملة
contact_idلامعرّف جهة التعامل (يجب أن تتبع نفس الشركة)
branch_idلامعرّف الفرع (يجب أن يتبع نفس الشركة)
dateلاتاريخ الفاتورة (YYYY-MM-DD)
due_dateلا*تاريخ الاستحقاق (≥ تاريخ الفاتورة). *إلزامي عند confirm=true أو set_as_paid=true.
e_contract_numberلارقم العقد الإلكتروني
show_qtyلاإظهار الكمية
show_unit_of_measureلاإظهار وحدة القياس (boolean)
confirmلاtrue لتأكيد الفاتورة وإرسالها لزاتكا مباشرة
set_as_paidلاtrue لتأكيدها وتسجيلها كمدفوعة
payment_method_idلامطلوب مع set_as_paid
payment_bank_account_idلاحساب بنكي اختياري يُربط بالدفعة عند set_as_paid

بنود الفاتورة (lines[])

الحقلالوصف
labelاسم/عنوان البند (إلزامي لكل بند)
descriptionوصف البند (إلزامي لكل بند)
priceسعر الوحدة (إلزامي لكل بند)
qtyالكمية (إلزامي لكل بند)
unit_of_measure_idمعرّف وحدة القياس (اختياري)
product_idمعرّف المنتج للربط بالمخزون (اختياري)
affect_stockهل يؤثر على المخزون (اختياري)
taxes[].idمعرّف الضريبة
taxes[].typeنوع تطبيق الضريبة: excluded / included / computed
discounts[].idمعرّف الخصم
حقول إلزامية في كل بند: يجب أن يحتوي كل بند على label و description و price و qty. إغفال أيٍّ منها يسبب خطأ عند الإنشاء.
أنواع تطبيق الضريبة (taxes.type):
  • excluded: الضريبة تُضاف فوق سعر البند (السعر لا يشمل الضريبة).
  • included: السعر يشمل الضريبة (لا تُضاف فوقه).
  • computed: تُحتسب الضريبة وتُستخرج من السعر الإجمالي.

مثال 1: إنشاء الفاتورة كمسودة

بدون confirm وset_as_paid؛ تُحفظ كمسودة (status=0) ولا تُرسَل لزاتكا. لا يوجد مسار تأكيد لاحق في هذه الواجهة — استخدم المسودة للتجربة فقط.

curl -X POST "https://v2.fwtrh.com/externalApi/invoices" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "type": "out_invoice",
    "contact_id": 123,
    "currency_id": 1,
    "date": "2026-06-06",
    "lines": [
      {
        "label": "Consulting service",
        "description": "Consultation hours",
        "price": 100,
        "qty": 2,
        "taxes": [ { "id": 5, "type": "excluded" } ]
      }
    ]
  }'

مثال 2: إنشاء الفاتورة كمؤكدة

مع confirm: true؛ تُؤكَّد (status=1) وتُرسَل لزاتكا. يجب إرسال due_date (≥ date) وإلا تفشل خطوة التأكيد وتبقى مسودة (422).

curl -X POST "https://v2.fwtrh.com/externalApi/invoices" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "type": "out_invoice",
    "contact_id": 123,
    "currency_id": 1,
    "date": "2026-06-06",
    "due_date": "2026-06-21",
    "confirm": true,
    "lines": [
      {
        "label": "Consulting service",
        "description": "Consultation hours",
        "price": 100,
        "qty": 2,
        "taxes": [ { "id": 5, "type": "excluded" } ]
      }
    ]
  }'

مثال 3: إنشاء الفاتورة بحالة مدفوعة

مع set_as_paid: true وpayment_method_id؛ تُؤكَّد وتُرسَل لزاتكا ثم تُسجَّل دفعة كاملة بقيمة الإجمالي. تتطلب أيضًا due_date.

curl -X POST "https://v2.fwtrh.com/externalApi/invoices" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "type": "out_invoice",
    "contact_id": 123,
    "currency_id": 1,
    "date": "2026-06-06",
    "due_date": "2026-06-21",
    "set_as_paid": true,
    "payment_method_id": 1,
    "lines": [
      {
        "label": "Consulting service",
        "description": "Consultation hours",
        "price": 100,
        "qty": 2,
        "taxes": [ { "id": 5, "type": "excluded" } ]
      }
    ]
  }'

استجابة الإنشاء

{
  "status": true,
  "invoice": {
    "id": 87361,
    "sequence": "INV-2026-0001",
    "type": "out_invoice",
    "invoice_type": "388",
    "document_type": "standard",
    "contact_id": 123,
    "currency_id": 1,
    "date": "2026-06-06",
    "due_date": "2026-06-21",
    "subtotal": 200.00,
    "discount": 0,
    "tax": 30.00,
    "total": 230.00,
    "balance": 230.00,
    "paid_amount": 0,
    "remaining_amount": 230.00,
    "status": 1,
    "payment_status": "unpaid",
    "sent_to_zatca": true,
    "sent_to_zatca_status": "PASS",
    "zatca_errors": null,
    "lines": [
      {
        "id": 91001,
        "label": "Consulting service",
        "description": "Consultation hours",
        "qty": 2,
        "price": 100,
        "subtotal": 200.00,
        "discount": 0,
        "tax": 30.00,
        "total": 230.00
      }
    ]
  }
}
سلوك التأكيد:
  • عند confirm=true: تُؤكَّد الفاتورة وتُرسَل تلقائيًا إلى زاتكا (لفواتير المبيعات).
  • عند set_as_paid=true: تُؤكَّد وتُرسَل لزاتكا ثم تُسجَّل دفعة كاملة (مع payment_method_id). لإنشاء فاتورة مدفوعة يكفي إرسال set_as_paid وحده.
  • يمكن إرسال confirm=true و set_as_paid=true معًا بأمان؛ تُرسَل الفاتورة لزاتكا مرة واحدة فقط ثم تُسجَّل الدفعة.
  • بدون أي منهما: تُحفظ كمسودة (draft) ولا يمكن تأكيدها لاحقًا عبر هذه الواجهة.

9.1 الخصومات (Discounts)

يدعم النظام نوعين من الخصم: خصم على مستوى البند (line-level) وخصم على مستوى الفاتورة كاملة (document-level).

أ) الخصم على مستوى البند (line-level)

يُطبَّق على بند محدّد، ويعتمد على خصومات مُعرَّفة مسبقًا في حساب الشركة. أولًا اجلب قائمة الخصومات لمعرفة المعرّفات (id):

curl -X GET "https://v2.fwtrh.com/externalApi/discounts" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"

لكل خصم حقل type بقيمة fixed (قيمة ثابتة) أو percent (نسبة مئوية)، وحقل amount. ثم مرّر معرّف الخصم داخل البند عبر discounts[].id (يمكن تمرير أكثر من خصم على نفس البند):

curl -X POST "https://v2.fwtrh.com/externalApi/invoices" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "type": "out_invoice",
    "contact_id": 123,
    "currency_id": 1,
    "date": "2026-06-06",
    "confirm": true,
    "lines": [
      {
        "label": "Consulting service",
        "price": 1000,
        "qty": 1,
        "taxes": [ { "id": 5, "type": "excluded" } ],
        "discounts": [ { "id": 12 } ]
      }
    ]
  }'
كيف يُحتسب خصم البند:
  • إذا كان نوع الخصم fixed: يُخصم مبلغ amount مباشرةً من قيمة البند.
  • إذا كان نوع الخصم percent: يُحتسب كنسبة amount% من القيمة الأساسية للبند (قبل الضريبة).
  • تُحتسب الضريبة على قيمة البند بعد خصمه (متوافق مع زاتكا).

ب) الخصم على مستوى الفاتورة (document-level)

يُطبَّق على إجمالي الفاتورة بعد جمع البنود. يُرسَل عبر الحقول التالية على مستوى جسم الطلب (وليس داخل البنود):

الحقلإلزامي؟الوصف
document_discount_typeلانوع الخصم: fixed (مبلغ ثابت) أو percent (نسبة).
document_discountلا*مبلغ الخصم الثابت. *إلزامي عند fixed. يُتجاهل عند percent (يُحسب تلقائيًا).
document_discount_percentلا*النسبة (0–100). *إلزامي عند percent؛ يحسب النظام المبلغ تلقائيًا منها.
document_discount_reasonلاسبب الخصم (نص اختياري).
طريقتان للخصم:
  • خصم نسبة: أرسل document_discount_type: "percent" وdocument_discount_percent (مثلًا 10). يحسب النظام مبلغ الخصم تلقائيًا = النسبة × (إجمالي البنود − خصومات البنود)، ولا حاجة لإرسال document_discount.
  • خصم مبلغ ثابت: أرسل document_discount_type: "fixed" وdocument_discount بالمبلغ المطلوب.

مثال: خصم نسبة 10% على كامل الفاتورة (إجمالي البنود 2000):

curl -X POST "https://v2.fwtrh.com/externalApi/invoices" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "type": "out_invoice",
    "contact_id": 123,
    "currency_id": 1,
    "date": "2026-06-06",
    "due_date": "2026-06-21",
    "confirm": true,
    "document_discount_type": "percent",
    "document_discount_percent": 10,
    "document_discount_reason": "Seasonal offer",
    "lines": [
      { "label": "Service A", "description": "Service A", "price": 1000, "qty": 1, "taxes": [ { "id": 5, "type": "excluded" } ] },
      { "label": "Service B", "description": "Service B", "price": 1000, "qty": 1, "taxes": [ { "id": 5, "type": "excluded" } ] }
    ]
  }'
كيف يُحتسب خصم الفاتورة:
  • الوعاء الخاضع = (إجمالي البنود − خصومات البنود).
  • عند النسبة: مبلغ الخصم = النسبة × الوعاء الخاضع (يُحسب تلقائيًا). عند الثابت: يُستخدم document_discount كما هو.
  • يُطرح مبلغ الخصم من الوعاء الخاضع، ثم تُعاد احتساب الضريبة على القيمة بعد الخصم (متوافق مع زاتكا).
  • يمكن الجمع بين خصومات البنود وخصم الفاتورة معًا في نفس الطلب.

9.2 الحقول الخاصة (Custom Fields)

يمكن للشركة تعريف حقول خاصة إضافية تظهر على الفاتورة (مثل: رقم الإيصال). تُرسَل قيم هذه الحقول عند إنشاء الفاتورة داخل كائن meta، حيث يكون المفتاح هو معرّف الحقل (id) كما هو معرّف لدى الشركة، والقيمة هي ما تريد حفظه. يحوّل النظام المعرّف داخليًا إلى الحقل الصحيح.

مهم — مفتاح الحقل: المفتاح داخل meta هو id الحقل الخاص (رقم). يجب أن يكون الحقل تابعًا لشركتك، وإلا يُرفض الطلب بالخطأ 422. احصل على المعرّفات من مسؤول الحساب أو من إعدادات الحقول الخاصة بالشركة.

مثال: إذا كان لدى الشركة حقل خاص معرّفه 155 (مثلًا «الإيصال»)، يُرسَل هكذا:

curl -X POST "https://v2.fwtrh.com/externalApi/invoices" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "type": "out_invoice",
    "contact_id": 123,
    "currency_id": 1,
    "date": "2026-06-06",
    "due_date": "2026-06-21",
    "confirm": true,
    "meta": {
      "155": "REC-2026-001"
    },
    "lines": [
      {
        "label": "خدمة استشارية",
        "description": "خدمة استشارية",
        "price": 100,
        "qty": 1,
        "taxes": [ { "id": 5, "type": "excluded" } ]
      }
    ]
  }'
ملاحظات:
  • يمكن إرسال أكثر من حقل خاص في نفس الكائن: "meta": { "155": "...", "160": "..." }.
  • meta اختياري بالكامل؛ إن لم يُرسَل تُنشأ الفاتورة دون حقول خاصة.
  • القيم تُحفظ كما هي (نصوص)؛ تأكّد من إرسالها بالنوع المتوقّع لدى الشركة.

9.3 قائمة الفواتير وعرض فاتورة

GET/externalApi/invoices

المعاملالوصف
status0 / 1 / 2 / -1
typeout_invoice · in_invoice · out_refund · in_refund
contactتصفية حسب contact_id
payment_statusunpaid / paid / partially paid — أو !paid للاستبعاد
invoice_typenote للإشعارات فقط، أو أي قيمة أخرى للفواتير (388)
invoiceمعرّف الفاتورة الأصل (لإشعارات مرتبطة بها)
qبحث بالرقم التسلسلي أو اسم العميل أو الإجمالي
sortBy / sortDescمثال: sortBy=date&sortDesc=true
perPage / nopaginateالترقيم أو إرجاع الكل
# قائمة فواتير المبيعات المؤكدة لعميل معيّن
curl -X GET "https://v2.fwtrh.com/externalApi/invoices?type=out_invoice&status=1&contact=123&perPage=20" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"

# عرض فاتورة واحدة
curl -X GET "https://v2.fwtrh.com/externalApi/invoices/87361" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"

قائمة الفواتير تُرجع حقولًا مختصرة (id, sequence, totals, status, payment_status, sent_to_zatca…). التفاصيل الكاملة للبنود والحقول الخاصة من مسار العرض أو من استجابة الإنشاء.

10. الإشعارات الدائنة والمدينة

الإشعار الدائن (Credit Note) يُستخدم للمرتجعات أو تخفيض قيمة فاتورة مبيعات مؤكدة، والإشعار المدين (Debit Note) يُستخدم لزيادة قيمة فاتورة. تُنشأ الإشعارات دائمًا من فاتورة قائمة.

10.1 إشعار دائن — مرتجع كامل

POST/externalApi/invoices/{id}/fullReverse

  • يعكس الفاتورة بالكامل (يجب أن تكون مؤكدة وليست مسودة).
  • لا يمكن عكس إشعار دائن مسبق، ولا فاتورة مُرتجعة بالكامل.
  • أرسل method=full لتأكيد الإشعار وإرساله لزاتكا مباشرة.
  • حقل اختياري date لتحديد تاريخ الإشعار.
curl -X POST "https://v2.fwtrh.com/externalApi/invoices/87361/fullReverse" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ "method": "full", "date": "2026-06-07" }'

10.2 إشعار دائن — مرتجع جزئي

POST/externalApi/invoices/{id}/partialReverse

الحقلإلزامي؟الوصف
methodنعمpartial / full
reasonلاسبب الإشعار
dateلاتاريخ الإشعار
lines[]لاالبنود المراد إرجاعها (label, description, price, qty, taxes[].id, discounts[].id)
confirmلاتأكيد الإشعار
set_as_paidلاتأكيد الإشعار وتسجيله كمدفوع (مع payment_method_id)
payment_method_idلامطلوب مع set_as_paid
curl -X POST "https://v2.fwtrh.com/externalApi/invoices/87361/partialReverse" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "method": "partial",
    "reason": "Partial return",
    "lines": [
      { "label": "Consulting service", "description": "Consulting service", "price": 100, "qty": 1,
        "taxes": [ { "id": 5 } ] }
    ]
  }'

10.3 إشعار مدين

POST/externalApi/invoices/{id}/debit

الحقلإلزامي؟الوصف
reasonلاسبب الإشعار المدين
dateلاتاريخ الإشعار
lines[]لاالبنود الإضافية (label, description, price, qty, taxes[].id, discounts[].id)
confirmلاتأكيد الإشعار
set_as_paidلاتأكيد الإشعار وتسجيله كمدفوع (مع payment_method_id)
payment_method_idلامطلوب مع set_as_paid
curl -X POST "https://v2.fwtrh.com/externalApi/invoices/87361/debit" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "reason": "Additional charges",
    "lines": [
      { "label": "Shipping fee", "description": "Shipping fee", "price": 50, "qty": 1,
        "taxes": [ { "id": 5 } ] }
    ]
  }'
سلوك التأكيد والدفع في الإشعارات:
  • عند confirm=true: يُؤكَّد الإشعار ويُرسَل لزاتكا.
  • عند set_as_paid=true: يُؤكَّد ويُرسَل لزاتكا ثم تُسجَّل دفعة كاملة (مع payment_method_id). ينطبق على الإشعار الدائن الجزئي والإشعار المدين.
  • يمكن إرسال confirm=true و set_as_paid=true معًا بأمان؛ يُرسَل الإشعار لزاتكا مرة واحدة فقط ثم تُسجَّل الدفعة.

استجابة الإشعار

{
  "status": true,
  "note": {
    "id": 87400,
    "sequence": "CN-2026-0001",
    "type": "out_refund",
    "invoice_type": "381",
    "invoice_id": 87361,
    "total": 230.00,
    "status": 1,
    "sent_to_zatca": true,
    "sent_to_zatca_status": "PASS"
  }
}

11. الإرسال إلى زاتكا (ZATCA)

إذا أنشأت الفاتورة بـ confirm=true أو set_as_paid=true يتم الإرسال تلقائيًا. استخدم هذا المسار فقط لفاتورة مؤكدة لم تُرسل بعد.

POST/externalApi/invoices/{id}/zatca

curl -X POST "https://v2.fwtrh.com/externalApi/invoices/87361/zatca" \
  -H "X-Auth-Username: [email protected]" \
  -H "X-Auth-Password: ********" \
  -H "Accept: application/json"
قواعد مهمة:
  • فواتير المشتريات (in_invoice) لا تُرسل إلى زاتكا.
  • لا يمكن إرسال فاتورة مسودة (status=0) قبل تأكيدها.
  • لا يمكن إعادة إرسال فاتورة سبق إرسالها بنجاح (sent_to_zatca=true).
  • عند استخدام confirm=true أو set_as_paid=true أثناء الإنشاء يتم الإرسال تلقائيًا.

حالات الإرسال (sent_to_zatca_status)

الحالةالمعنى
PASSتم القبول من زاتكا بنجاح
WARNINGمقبول مع وجود تحذيرات
ERRORمرفوض — راجع حقل zatca_errors في الاستجابة

عند حدوث ERROR، تحتوي استجابة الفاتورة على مصفوفة zatca_errors بصيغة: التصنيف - الكود - الرسالة.

12. الأخطاء الشائعة ومعالجتها

رمز الحالةالسبب المحتمل والحل
401بيانات اعتماد مفقودة أو غير صحيحة — تحقق من X-Auth-Username / X-Auth-Password
403محاولة الوصول لمورد لا يتبع شركتك (contact/tax/discount/branch)
404المسار أو المورد غير موجود — تحقّق من الـ id والـ Base URL
422خطأ تحقّق من البيانات — راجع حقل error أو errors في الاستجابة
500خطأ خادم — أعد المحاولة لاحقًا واحتفظ بجسم الطلب للدعم

أمثلة رسائل 422 الشائعة

الرسالة / المعنىالمعالجة
Please update contact VAT number…حدّث vat للعميل: 15 خانة تبدأ وتنتهي بـ 3
Please provide valid contact/tax/discount/branch id…المعرّف لا يخص شركتك — اجلبه من مسارات الـ lookup
The taxes entered has a category that does not match…استخدم ضريبة sale لـ out_invoice و purchase لـ in_invoice
Invalid custom field id…مفتاح meta ليس id حقل خاص تابع للشركة
Invoice can't be sent to ZATCA before being confirmedأرسل confirm=true عند الإنشاء؛ لا يوجد تأكيد لاحق
This invoice is already sent to zatcaلا تعِد الإرسال — اقرأ sent_to_zatca_status
Draft Invoices can't be reversedأكّد الفاتورة أولًا قبل المرتجع
Invoice has been fully refundedلا يمكن إصدار مرتجع إضافي
Street / Country / City is required for ZATCA invoicesأكمل عنوان العميل قبل إنشاء فاتورة زاتكا
{
  "status": false,
  "error": ["Please update contact VAT number. It must be 15 digits starting and ending with 3."],
  "invoice": { "id": 87361, "status": 0 }
}

نصائح عامة للتكامل:

  • تحقّق دائمًا من حقل status في الاستجابة قبل المتابعة.
  • خزّن معرّفات (IDs) العملاء والفواتير العائدة من المنصة لربطها بسجلاتك.
  • تأكّد أن جميع المعرّفات (currency_id, tax_id, contact_id, branch_id) تخص شركتك.
  • للفواتير الخاضعة لزاتكا، تأكّد من صحة الرقم الضريبي للعميل قبل التأكيد.

13. التدفّق الكامل — من الحساب إلى زاتكا

اتبع الخطوات بالترتيب. كل خطوة تعتمد على معرّفات من الخطوة السابقة. السكربت في نهاية القسم ينفّذ المسار السعيد كاملًا.

1
إنشاء حساب على المنصة سجّل من الواجهة كأي مستخدم. يُنشأ company_id تلقائيًا. فعّل زاتكا من إعدادات الشركة إن كنت سترسل فواتير مبيعات.
2
اختبار المصادقة GET /contacts بترويستي X-Auth-*. نجاح 200 يعني أن الاعتماد صحيح والشركة مربوطة.
3
جلب المعرّفات المرجعية وخزنها currency_id (SAR عادة 1) · tax_id لضريبة بيع 15% · payment_method_id إن كنت ستعلّم الفاتورة مدفوعة · country_id / city_id لعنوان العميل.
4
ابحث عن العميل ثم أنشئه عند الحاجة GET /contacts?vat=… فإن وُجد خذ id. وإلا POST /contacts ثم خزّن id. لا تُنشئ عميلاً مكررًا.
5
أنشئ فاتورة المبيعات POST /invoices بالنوع out_invoice و contact_id و currency_id والبنود. للإنتاج استخدم confirm=true مع due_date ليتم الإرسال لزاتكا فورًا. للتجربة اتركها مسودة.
6
اقرأ حالة زاتكا من استجابة الإنشاء: sent_to_zatca و sent_to_zatca_status. PASS أو WARNING = مقبول. ERROR = راجع zatca_errors ولا تعِد الإرسال إلا بعد تصحيح البيانات في فاتورة جديدة (لا يمكن إعادة إرسال نفس الفاتورة بعد نجاحها).
7
عند الحاجة: إشعار دائن أو مدين من فاتورة مؤكدة فقط: fullReverse للمرتجع الكامل، partialReverse لجزء من البنود، debit لزيادة القيمة. أرسل method=full أو confirm=true لإرسال الإشعار لزاتكا.

متى تستخدم أي وضع إنشاء؟

الهدفماذا ترسلالنتيجة
تجربة / مراجعة قبل زاتكابدون confirm وبدون set_as_paidمسودة status=0 — لن تُرسل لزاتكا، ولا يمكن تأكيدها لاحقًا عبر هذه الواجهة
فاتورة مؤكدة ومُرسلةconfirm: true + due_datestatus=1 ويُرسل لزاتكا تلقائيًا
فاتورة مدفوعة فورًاset_as_paid: true + payment_method_id + due_dateتأكيد + زاتكا + دفعة كاملة. يكفي set_as_paid وحده

سكربت جاهز للنسخ (المسار السعيد)

استبدل البريد وكلمة المرور والمعرّفات بعد جلبها من قسم البيانات المرجعية. السكربت يبحث عن العميل بالرقم الضريبي، ينشئه إن لزم، ثم ينشئ فاتورة مؤكدة.

BASE="https://v2.fwtrh.com/externalApi"
USER="[email protected]"
PASS="********"

# 1) جلب ضرائب المبيعات (خذ id ضريبة 15%)
curl -s -X GET "$BASE/taxes?type=S&nopaginate=1" \
  -H "X-Auth-Username: $USER" -H "X-Auth-Password: $PASS" -H "Accept: application/json"

# 2) البحث عن العميل
VAT="300000000000003"
FOUND=$(curl -s -X GET "$BASE/contacts?vat=$VAT" \
  -H "X-Auth-Username: $USER" -H "X-Auth-Password: $PASS" -H "Accept: application/json")

# 3) إن لم يوجد: أنشئه
# CONTACT_ID=$(... من FOUND أو من استجابة الإنشاء)
curl -s -X POST "$BASE/contacts" \
  -H "X-Auth-Username: $USER" -H "X-Auth-Password: $PASS" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
    "type": "company",
    "name": "Client Trading Co.",
    "account_type": "taxable",
    "vat": "300000000000003",
    "registry": "1234567890",
    "identitty_types": "CRN",
    "email": "[email protected]",
    "phone": "0512345678",
    "country_id": 1,
    "city_id": 10,
    "street": "King Fahd Rd",
    "district": "Al Olaya",
    "building_no": "1234",
    "zip": "12345",
    "currency_id": 1
  }'

# 4) إنشاء فاتورة مؤكدة (تُرسل لزاتكا)
curl -s -X POST "$BASE/invoices" \
  -H "X-Auth-Username: $USER" -H "X-Auth-Password: $PASS" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
    "type": "out_invoice",
    "contact_id": 123,
    "currency_id": 1,
    "date": "2026-09-26",
    "due_date": "2026-10-11",
    "confirm": true,
    "lines": [
      {
        "label": "Consulting service",
        "description": "Consultation hours",
        "price": 100,
        "qty": 2,
        "taxes": [ { "id": 5, "type": "excluded" } ]
      }
    ]
  }'

# 5) (اختياري) مرتجع كامل لاحقًا
# curl -s -X POST "$BASE/invoices/87361/fullReverse" \
#   -H "X-Auth-Username: $USER" -H "X-Auth-Password: $PASS" \
#   -H "Content-Type: application/json" -H "Accept: application/json" \
#   -d '{ "method": "full", "date": "2026-09-27" }'

مثال JavaScript (fetch)

const BASE = "https://v2.fwtrh.com/externalApi";
const headers = {
  "X-Auth-Username": "[email protected]",
  "X-Auth-Password": "********",
  "Accept": "application/json",
  "Content-Type": "application/json",
};

async function upsertContact(payload) {
  const search = await fetch(`${BASE}/contacts?vat=${payload.vat}`, { headers }).then(r => r.json());
  const rows = search.data || search;
  if (Array.isArray(rows) && rows.length) return rows[0].id;
  const created = await fetch(`${BASE}/contacts`, {
    method: "POST", headers, body: JSON.stringify(payload),
  }).then(r => r.json());
  return created.contact.id;
}

async function createConfirmedInvoice(contactId, taxId) {
  const res = await fetch(`${BASE}/invoices`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      type: "out_invoice",
      contact_id: contactId,
      currency_id: 1,
      date: "2026-09-26",
      due_date: "2026-10-11",
      confirm: true,
      lines: [{
        label: "Consulting service",
        description: "Consultation hours",
        price: 100,
        qty: 2,
        taxes: [{ id: taxId, type: "excluded" }],
      }],
    }),
  });
  return res.json();
}

قائمة تحقق قبل الإطلاق

  • حساب المنصة فعّال، وشهادة زاتكا مربوطة إن كنت سترسل فواتير مبيعات.
  • X-Auth-Username / X-Auth-Password يعملان على GET /contacts.
  • تم تخزين currency_id و tax_id (sale) و payment_method_id.
  • البحث عن العميل بـ vat قبل كل إنشاء، مع جدول ربط محلي.
  • عملاء taxable لديهم vat صحيح + عنوان زاتكا (شارع، دولة، مدينة، رمز بريدي).
  • كل بند فيه label و description و price و qty، والضريبة من نوع sale لفاتورة المبيعات.
  • confirm=true يصاحبه due_date ≥ date. لا تعتمد على تأكيد لاحق.
  • بعد الإنشاء تقرأ status و sent_to_zatca_status وتخزّن invoice.id و sequence.
  • تعالج 401 / 422 / zatca_errors ولا تعيد إرسال فاتورة ناجحة.
ملاحظة ختامية: جميع العمليات تتم ضمن نطاق شركة المستخدم المُصادَق به فقط (company_id الخاص به)، ولا يمكن الوصول لبيانات شركات أخرى.