المطورون حالات الاستخدام

واجهة برمجية لفوترة الاشتراكات في السعودية

أصدر فاتورة ضريبية سعودية لكل دفعة اشتراك: المسار، وربط الحقول، وإعادة المحاولة، والعملاء من المنشآت ومن خارج المملكة، وما ترسله لعميلك.

المسار

  1. يحصّل نظام الفوترة لديك المبلغ من العميل عبر مزود الدفع لديك، وبقواعدك أنت.
  2. عند النجاح تستدعي مهمة في الخلفية POST /invoices مع معرّف الدفعة في external_reference.
  3. حين يصبح zatca.deliverable مساويًا true أرسل public_url مع بريد الإيصال.
  4. للاسترداد أو تغيير الباقة بعد الإصدار، أنشئ إشعارًا دائنًا عبر POST /invoices/{id}/notes (أو من لوحة تحكم وندرفاتورة).

ربط الاشتراك بحقول الفاتورة

بياناتك حقل الواجهة
معرّف الدفعة أو فترة الفوترة external_reference
تاريخ الدفع issue_date
الباقة والفترة items[].description، مثل "باقة Pro، نوفمبر 2026"
السعر قبل الضريبة items[].unit_price
قسيمة خصم على الطلب كله document_discount_type وdocument_discount_value
العميل customer.id (يُحفظ بعد أول فاتورة) أو بيانات مباشرة
العملة currency وexchange_rate

العملاء من المنشآت والمستهلكون

العميل المنشأة الذي له رقم ضريبي سعودي صالح يحصل على فاتورة ضريبية تعتمدها الهيئة قبل أن ترسلها. أنشئ هؤلاء العملاء مرة واحدة عبر POST /customers (أو من لوحة التحكم) بعنوانهم الوطني الكامل وأعد استخدام المعرّف. أما المستهلك، أو العميل بلا رقم ضريبي سعودي، فيحصل على فاتورة مبسطة ترسلها فورًا.

العملاء خارج المملكة

اجعل customer.country رمز دولتهم. الخدمات المقدمة لعميل خارج المملكة كثيرًا ما تخضع للنسبة الصفرية، وعندها أرسل vat_rate: 0 مع vat_exemption_code: "VATEX-SA-33". أما هل تنطبق النسبة الصفرية على توريدك فسؤال لمستشارك الضريبي.

{
  "issue_date": "2026-11-03",
  "currency": "USD",
  "exchange_rate": 3.75,
  "customer": { "name": "Example Inc.", "country": "US" },
  "items": [{
    "description": "Software licence (export of services)",
    "quantity": 1,
    "unit_price": 400,
    "vat_rate": 0,
    "vat_exemption_code": "VATEX-SA-33"
  }]
}

لا تفوتر مرتين أبدًا

مهام الفوترة تعيد المحاولة. ومع external_reference يساوي معرّف الدفعة، تتلقى المهمة المعادة الفاتورة التي أنشأتها المحاولة الأولى (200، "replayed": true).

import os, time, uuid
import requests

API = "https://fatoora.tw.sa/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['WONDERFATOORA_API_KEY']}",
           "Accept": "application/json"}

def create_invoice(order):
    payload = {
        # Your own order id: a retry after a timeout returns the invoice
        # the first attempt created instead of issuing a second one.
        "external_reference": order["id"],
        "issue_date": order["date"],
        "items": order["items"],
    }
    for attempt in range(5):
        try:
            res = requests.post(f"{API}/invoices", json=payload, headers=HEADERS, timeout=60)
        except requests.RequestException:
            time.sleep(2 ** attempt)          # network error: same payload, try again
            continue
        if res.status_code == 429:
            time.sleep(int(res.headers.get("Retry-After", "5")))
            continue
        if res.status_code >= 500:
            time.sleep(2 ** attempt)
            continue
        return res.json()                     # 200/201, or a 4xx to fix, not retry
    raise RuntimeError("WonderFatoora API unavailable, will retry later")
async function createInvoice(order) {
  const payload = {
    external_reference: order.id, // safe to retry
    issue_date: order.date,
    items: order.items,
  };
  for (let attempt = 0; attempt < 5; attempt++) {
    let res;
    try {
      res = await fetch("https://fatoora.tw.sa/api/v1/invoices", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.WONDERFATOORA_API_KEY}`,
          "Content-Type": "application/json",
          Accept: "application/json",
        },
        body: JSON.stringify(payload),
        signal: AbortSignal.timeout(60_000),
      });
    } catch {
      await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
      continue;
    }
    if (res.status === 429) {
      await new Promise((r) => setTimeout(r, Number(res.headers.get("Retry-After") ?? 5) * 1000));
      continue;
    }
    if (res.status >= 500) {
      await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
      continue;
    }
    return res.json(); // 200/201, or a 4xx to fix rather than retry
  }
  throw new Error("WonderFatoora API unavailable, retry later");
}

جاهز للبدء؟

الوصول إلى الواجهة البرمجية ضمن باقات: الأعمال والمؤسسات. أنشئ مفتاحًا من الإعدادات ← مفاتيح API وأرسل أول طلب خلال دقائق.