تسجيل عملية بيع
عملية البيع عن طريق الـ API أربع خطوات. وكل خطوة في الأسفل تعرض الطلب كاملاً والرد كاملاً، بمفتاح اختبار على بيئة الاختبار.
- اقرأ الكتالوج: الفرع والأصناف وطريقة الدفع.
- ابحث عن العميل، أو أنشئه.
- أصدر الفاتورة، مع الدفع أو بدونه.
- سجّل الدفعة لاحقاً إذا لم تُدفع الفاتورة عند الإصدار.
يحتاج المفتاح الصلاحيات branches:read و items:read و payment_methods:read و customers:write و sales_invoices:write، و payments:write للخطوة 4.
المعرّفات في الأمثلة من البيانات الجاهزة في بيئة الاختبار متى وُجدت. أما قيم number للفواتير وسندات القبض فمن تأليفنا، لأن كل تاجر يحدد ترقيمه بنفسه.
1. اقرأ الكتالوج
تحدد الفاتورة فرعها، وصنف كل بند، وطريقة الدفع، بمعرّفاتها. خذ هذه المعرّفات من الكتالوج، ولا تخمّنها أبداً:
| الـ Endpoint | الصلاحية | ماذا يعطيك |
|---|---|---|
GET /v1/branches | branches:read | الفروع التي يعمل فيها مفتاحك: branchId |
GET /v1/items | items:read | الأصناف التي يبيعها التاجر: lines[].itemId |
GET /v1/payment-methods | payment_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 جديد |
كل الأكواد في أكواد الأخطاء.