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

تسجيل عملية بيع

عملية البيع عن طريق الـ API أربع خطوات. وكل خطوة في الأسفل تعرض الطلب كاملاً والرد كاملاً، بمفتاح اختبار على بيئة الاختبار.

  1. اقرأ الكتالوج: الفرع والأصناف وطريقة الدفع.
  2. ابحث عن العميل، أو أنشئه.
  3. أصدر الفاتورة، مع الدفع أو بدونه.
  4. سجّل الدفعة لاحقاً إذا لم تُدفع الفاتورة عند الإصدار.

يحتاج المفتاح الصلاحيات branches:read و items:read و payment_methods:read و customers:write و sales_invoices:write، و payments:write للخطوة 4.

المعرّفات في الأمثلة من البيانات الجاهزة في بيئة الاختبار متى وُجدت. أما قيم number للفواتير وسندات القبض فمن تأليفنا، لأن كل تاجر يحدد ترقيمه بنفسه.

1. اقرأ الكتالوج​

تحدد الفاتورة فرعها، وصنف كل بند، وطريقة الدفع، بمعرّفاتها. خذ هذه المعرّفات من الكتالوج، ولا تخمّنها أبداً:

الـ Endpointالصلاحيةماذا يعطيك
GET /v1/branchesbranches:readالفروع التي يعمل فيها مفتاحك: branchId
GET /v1/itemsitems:readالأصناف التي يبيعها التاجر: lines[].itemId
GET /v1/payment-methodspayment_methods:readطرق الدفع لدى التاجر: payment.paymentMethodId

تحتفظ بيئة الاختبار بالمعرّفات الفعلية للفروع والأصناف وطرق الدفع التي نسختها، فالمعرّفات التي تربطها في بيئة الاختبار صالحة على البيانات الفعلية.

2. ابحث عن العميل أو أنشئه​

كل فاتورة تحدد عميلاً، فلا يدعم الـ API البيع لعميل نقدي غير مسجّل. ابحث عن العميل برقم جواله، أو بقيمة referenceId التي أعطيته إياها:

curl "https://api.kosoor.com.sa/v1/customers?phone=%2B966500000001" \
-H "Authorization: Bearer ksr_test_..."

إذا كانت data فارغة فأنشئ العميل بـ POST /v1/customers. وإذا أُرسل طلبان منك في الوقت نفسه وسُجّل الرقم بينهما، يرجع طلب الإنشاء 409 phone_taken، ويحمل existingId معرّف العميل الذي تستخدمه. الطلبان وكل القواعد في صفحة العملاء.

تستخدم الأمثلة التالية عميلين جاهزين في بيئة الاختبار: Sandbox Person (...0001) وهو فرد، و Sandbox Standard Co (...0002) وهو شركة.

3. أصدر الفاتورة​

يُنشئ POST /v1/sales-invoices الفاتورة ويصدرها في طلب واحد، بلا مسودة. وكل طلب POST يحمل Idempotency-Key: إذا ضاع الرد فأرسل الطلب نفسه بالمفتاح نفسه، فيرجع لك الرد على الفاتورة نفسها بدل أن تُنشأ فاتورة ثانية. التفاصيل في منع تكرار الطلبات.

عميل فرد يدفع المبلغ كاملاً​

العميل فرد، فالفاتورة مبسطة. و payment بدون amount يدفع الفاتورة كلها. والسعر هنا شامل الضريبة، لذلك قيمة pricesIncludeVat هي true.

curl https://api.kosoor.com.sa/v1/sales-invoices \
-H "Authorization: Bearer ksr_test_..." \
-H "Idempotency-Key: 4e9f2a7c-1b3d-4c5e-8f6a-9b0c1d2e3f4a" \
-H "Content-Type: application/json" \
-d '{
"customerId": "5a4db0c5-0000-4000-8000-000000000001",
"branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"pricesIncludeVat": true,
"lines": [
{ "itemId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f", "quantity": 1, "unitPrice": 115 }
],
"payment": { "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9" },
"referenceId": "order-20417"
}'

الرد 201 Created:

{
"id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
"object": "salesInvoice",
"number": "INV-1042",
"issueDate": "2026-11-07",
"supplyDate": null,
"dueDate": null,
"branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"customerId": "5a4db0c5-0000-4000-8000-000000000001",
"currency": "SAR",
"subtotal": 100,
"discountTotal": 0,
"vatTotal": 15,
"total": 115,
"amountPaid": 115,
"amountDue": 0,
"lines": [
{
"itemId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
"name": "Full wash",
"description": null,
"quantity": 1,
"unitPrice": 100,
"unitDiscount": 0,
"vatRate": 15,
"vatAmount": 15,
"lineTotal": 115
}
],
"notes": null,
"costCentreId": null,
"referenceId": "order-20417",
"metadata": {},
"createdVia": "api",
"zatca": {
"kind": "simplified",
"status": "pending",
"uuid": null,
"counter": null,
"hash": null,
"qr": null,
"submittedAt": null,
"clearedAt": null,
"reportedAt": null,
"errors": []
}
}

اقرأ من الرد:

  • أسعار البنود قبل الضريبة. الـ 115 التي أرسلتها شاملة الضريبة تظهر unitPrice: 100 مع vatAmount: 15.
  • total هو ما يدفعه العميل. قارنه بما يتوقعه نظامك: الخصم الذي ينزل بالسعر عن الحد الأدنى للصنف يُخفَّض، والسعر الشامل للضريبة قد يغيّر الإجمالي بهللة. التفاصيل في فواتير المبيعات.
  • قيمة zatca.status هي pending. الفاتورة المبسطة تُبلَّغ بها هيئة الزكاة والضريبة والجمارك بعد الرد، ثم تصبح reported. وتبقى حقول المستند null حتى توقّعه كسور. التفاصيل في نتائج الهيئة.

ويبقى المبلغ على الفاتورة نفسها (amountPaid)، فالبيع لعميل فرد مدفوعاً عند الإصدار لا تُنشأ له دفعة منفصلة.

شركة بالآجل​

العميل شركة، فالفاتورة ضريبية، وتعتمدها الهيئة قبل أن ترد كسور. وبدون payment تُسجَّل الفاتورة كلها على حساب الشركة. والأسعار هنا قبل الضريبة.

curl https://api.kosoor.com.sa/v1/sales-invoices \
-H "Authorization: Bearer ksr_test_..." \
-H "Idempotency-Key: 0c5b7d3e-9f2a-4b1c-8d6e-3a4f5b6c7d8e" \
-H "Content-Type: application/json" \
-d '{
"customerId": "5a4db0c5-0000-4000-8000-000000000002",
"branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"pricesIncludeVat": false,
"lines": [
{ "itemId": "8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a", "quantity": 2, "unitPrice": 100, "description": "Plates ABC 1234 and XYZ 5678" }
],
"dueDate": "2026-12-07",
"notes": "Fleet contract 2026-14",
"referenceId": "order-20418",
"metadata": { "fleetContract": "2026-14" }
}'

الرد 201 Created:

{
"id": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f",
"object": "salesInvoice",
"number": "INV-1043",
"issueDate": "2026-11-07",
"supplyDate": null,
"dueDate": "2026-12-07",
"branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"customerId": "5a4db0c5-0000-4000-8000-000000000002",
"currency": "SAR",
"subtotal": 200,
"discountTotal": 0,
"vatTotal": 30,
"total": 230,
"amountPaid": 0,
"amountDue": 230,
"lines": [
{
"itemId": "8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a",
"name": "Fleet wash",
"description": "Plates ABC 1234 and XYZ 5678",
"quantity": 2,
"unitPrice": 100,
"unitDiscount": 0,
"vatRate": 15,
"vatAmount": 30,
"lineTotal": 230
}
],
"notes": "Fleet contract 2026-14",
"costCentreId": null,
"referenceId": "order-20418",
"metadata": { "fleetContract": "2026-14" },
"createdVia": "api",
"zatca": {
"kind": "standard",
"status": "cleared",
"uuid": "e7f8a9b0-c1d2-4e3f-a4b5-c6d7e8f9a0b1",
"counter": 18,
"hash": "zXab+32ksuccBzrDiE27f65AmOvbZ2ixbnSSRnoRKzA=",
"qr": "ARBTYW5kYm94IE1lcmNoYW50Ag8zOTk5OTk5OTk5MDAwMDMDFDIwMjYtMTEtMDdUMDk6NDE6MjJaBAYyMzAuMDAFBTMwLjAw",
"submittedAt": "2026-11-07T09:41:21.884Z",
"clearedAt": "2026-11-07T09:41:22.517Z",
"reportedAt": null,
"errors": []
}
}

قيمة zatca.status هي cleared، ويحمل zatca.qr رمز QR الذي أرجعته الهيئة. وإذا لم ترد الهيئة في الوقت المحدد فالرد يبقى 201 لكن بـ status: "pending"، وتعتمدها كسور لاحقاً. وإذا رفضتها الهيئة فلا يُحفظ شيء، ويرجع الطلب 422 zatca_rejected. الحالتان موضحتان في نتائج الهيئة.

البيع بالآجل للشركات فقط. والفاتورة لعميل فرد بدون payment، أو مع payment.amount أقل من الإجمالي، ترجع 422 on_account_not_allowed.

استلام جزء من المبلغ عند الإصدار​

إذا كانت payment.amount أقل من الإجمالي، يُدفع هذا المبلغ عند الإصدار، ويبقى الباقي على حساب الشركة:

{
"paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
"amount": 100
}

والمبلغ الأكبر من الإجمالي يرجع 400 invalid_parameter. ولدفع الفاتورة كاملة لا ترسل amount، بدل أن ترسل الإجمالي الذي حسبته بنفسك.

4. سجّل الدفعة لاحقاً​

عندما تدفع الشركة، سجّل المبلغ بـ POST /v1/payments وخصّصه للفاتورة:

curl https://api.kosoor.com.sa/v1/payments \
-H "Authorization: Bearer ksr_test_..." \
-H "Idempotency-Key: 7a1b2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d" \
-H "Content-Type: application/json" \
-d '{
"customerId": "5a4db0c5-0000-4000-8000-000000000002",
"branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
"amount": 230,
"date": "2026-11-09",
"allocations": [
{ "invoiceId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f", "amount": 230 }
],
"referenceId": "bank-tx-55190"
}'

الرد 201 Created:

{
"id": "d3e4f5a6-b7c8-4d9e-8f0a-2b3c4d5e6f7a",
"object": "payment",
"number": "RCT-0031",
"date": "2026-11-09",
"customerId": "5a4db0c5-0000-4000-8000-000000000002",
"branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
"amount": 230,
"discount": 0,
"allocations": [
{ "invoiceId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f", "amount": 230 }
],
"unallocatedAmount": 0,
"notes": null,
"referenceId": "bank-tx-55190",
"metadata": {},
"createdVia": "api"
}

بعدها يعرض GET /v1/sales-invoices/c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f القيمتين amountPaid: 230 و amountDue: 0. والمبلغ الذي لا تخصصه يبقى رصيداً دائناً للعميل، ويظهر في unallocatedAmount. كل القواعد في صفحة المدفوعات.

5. أعطِ العميل الفاتورة​

يرجع GET /v1/sales-invoices/{id}/pdf الفاتورة بمقاس A4. ملف PDF للفاتورة المبسطة جاهز فوراً، أما الفاتورة الضريبية فيرجع ملفها 409 invoice_not_cleared حتى تعتمدها الهيئة.

ما الذي قد يحدث​

الخطأالمعنىماذا تفعل
422 customer_address_incompleteالفاتورة الضريبية تحتاج عنوان الشركة كاملاً مع الرقم الضريبي أو رقم الهوية، والحقل missing يوضح الناقصأكمل الناقص بـ PATCH /v1/customers/{id}، ثم أرسل الفاتورة مرة أخرى
422 on_account_not_allowedعميل فرد لم يدفع المبلغ كاملاًأرسل payment بدون amount
422 credit_limit_exceededحساب الشركة سيتجاوز حد الائتماناستلم المبلغ عند الإصدار، أو راجع التاجر
422 customer_inactiveعطّل التاجر هذا العميلراجع التاجر
422 item_not_sellableالصنف اشتراك، أو غير معروض للبيعاستخدم صنفاً آخر
400 missing_parameter على lines[i].unitPriceالصنف مسعّر حسب حجم السيارةأرسل unitPrice
403 branch_not_allowedالمفتاح مقصور على فروع أخرىاستخدم فرعاً من GET /v1/branches
422 branch_not_onboardedالفرع غير مربوط بالهيئة، فلا يستطيع إصدار فاتورة ضريبيةراجع التاجر
422 zatca_rejectedرفضت الهيئة الفاتورة الضريبية، ولم يُحفظ شيءاقرأ zatcaErrors، وصحّح البيانات، ثم أرسلها مرة أخرى بـ Idempotency-Key جديد

كل الأكواد في أكواد الأخطاء.