دليل ربط واجهة برمجة التطبيقات (API)
منصة فوترة — دليل المطوّر لتكامل الأنظمة المحاسبية الخارجية
1. نظرة عامة
يوضّح هذا الدليل خطوات ربط نظامكم المحاسبي الخاص بمنصة فوترة عبر واجهة API الخارجية. تغطي هذه الواجهة المسارات اللازمة لإنشاء جهات التعامل (العملاء/الموردين)، ثم إنشاء فواتير المبيعات والمشتريات، ثم إصدار الإشعارات الدائنة (المرتجعات) والإشعارات المدينة، وأخيرًا إرسال الفواتير إلى هيئة الزكاة والضريبة والجمارك (ZATCA).
ترتيب التكامل الموصى به:
- الخطوة 1: إنشاء حساب على المنصة (يُربط تلقائيًا بـ company_id خاص بالعميل).
- الخطوة 2: المصادقة والتأكد من نجاح الاتصال.
- الخطوة 3: جلب البيانات المرجعية (العملات، الضرائب، طرق الدفع).
- الخطوة 4: إنشاء جهات التعامل (العملاء).
- الخطوة 5: إنشاء فواتير المبيعات (مع إمكانية التأكيد والإرسال لزاتكا).
- الخطوة 6: إصدار الإشعارات الدائنة والمدينة عند الحاجة.
- الخطوة 7: متابعة حالة الإرسال إلى زاتكا.
2. الحساب و company_id
ينشئ العميل حسابًا عاديًا على المنصة كأي مستخدم. بمجرد إنشاء الحساب يُنشأ له سجل شركة خاص به ويحصل على company_id فريد.
عمليًا هذا يعني:
- جميع جهات التعامل والفواتير التي يُنشئها العميل تُنسب تلقائيًا إلى شركته.
- أي معرّفات يستخدمها (contact_id, currency_id, tax_id, branch_id) يجب أن تخص شركته، وإلا تُرفض العملية.
3. المصادقة (Authentication)
تستخدم الواجهة الخارجية مصادقة عبر ترويسات HTTP مخصّصة. يجب إرسال بيانات اعتماد المستخدم (البريد الإلكتروني وكلمة المرور) في كل طلب عبر الترويستين التاليتين:
| الترويسة (Header) | الوصف |
|---|---|
X-Auth-Username | البريد الإلكتروني للمستخدم في المنصة |
X-Auth-Password | كلمة مرور المستخدم |
Accept | application/json |
Content-Type | application/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
}
}
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 | لا | معرّف الجهة الأم (إن وُجدت) |
نوع مستند زاتكا يُحدَّد تلقائيًا من العميل: 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 | تصفية حسب الجهة الأم |
مثال
# 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 } ] }'
9. فواتير المبيعات (Invoices)
إنشاء فاتورة مبيعات هو المسار الأساسي للتكامل. حدّد النوع، اربط العميل، أرسل البنود، ثم اختر المسودة أو التأكيد أو التسجيل كمدفوعة.
أنواع الفواتير والحالات
type | المعنى | زاتكا؟ |
|---|---|---|
out_invoice | فاتورة مبيعات (invoice_type=388) | نعم |
in_invoice | فاتورة مشتريات | لا |
out_refund | إشعار دائن لمبيعات (invoice_type=381) — يُفضَّل إنشاؤه عبر fullReverse/partialReverse | نعم |
in_refund | إشعار دائن لمشتريات | لا |
| الحقل | القيم |
|---|---|
status | 0 = draft · 1 = confirmed · 2 = cancelled · -1 = void |
payment_status | unpaid · partially paid · paid |
document_type | standard / simplified — يُحدَّد تلقائيًا من العميل |
invoice_type | 388 فاتورة · 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. إغفال أيٍّ منها يسبب خطأ عند الإنشاء.
- 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
| المعامل | الوصف |
|---|---|
status | 0 / 1 / 2 / -1 |
type | out_invoice · in_invoice · out_refund · in_refund |
contact | تصفية حسب contact_id |
payment_status | unpaid / paid / partially paid — أو !paid للاستبعاد |
invoice_type | note للإشعارات فقط، أو أي قيمة أخرى للفواتير (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. التدفّق الكامل — من الحساب إلى زاتكا
اتبع الخطوات بالترتيب. كل خطوة تعتمد على معرّفات من الخطوة السابقة. السكربت في نهاية القسم ينفّذ المسار السعيد كاملًا.
متى تستخدم أي وضع إنشاء؟
| الهدف | ماذا ترسل | النتيجة |
|---|---|---|
| تجربة / مراجعة قبل زاتكا | بدون confirm وبدون set_as_paid | مسودة status=0 — لن تُرسل لزاتكا، ولا يمكن تأكيدها لاحقًا عبر هذه الواجهة |
| فاتورة مؤكدة ومُرسلة | confirm: true + due_date | status=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 ولا تعيد إرسال فاتورة ناجحة.