> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneclickdz.com/llms.txt
> Use this file to discover all available pages before exploring further.

# استكشاف الفواتير

> اسأل مُصدِر الفاتورة عمّا يدين به حساب ما، واقرأ النتيجة

<div dir="ltr">
  ## نظرة عامة

  الاستكشاف هو نصف القراءة في دفع الفواتير: تسأل مُصدِر الفاتورة عمّا يدين به حساب ما حالياً، فتحصل على قائمة بالفواتير القابلة للدفع. لا يُخصم شيء، ولا يُلتزم بشيء.

  ويجري ذلك على جزأين. `POST /v3/bills/discover` يقبل الطلب ويعطيك `transactionId`. أما الفواتير نفسها فتصل على تلك المعاملة بعد لحظة، حين تصبح `status` الخاصة بها `READY`.

  <Warning>
    الرمز `200` من `discover` هو **إقرار بالاستلام**. فهو لا يحمل أي فواتير ولا يقول شيئاً عمّا يدين به الحساب. اقرأ المعاملة قبل أن تخبر عميلك بأي شيء.
  </Warning>

  ## بناء الطلب

  ثلاثة حقول، جميعها مطلوبة.

  ```json theme={null}
  {
    "partner": "ADE",
    "account": { "reference": "0123456789012345678901234" },
    "ref": "disc-inv-2026-0042"
  }
  ```

  | الحقل     | القاعدة                                                                              |
  | --------- | ------------------------------------------------------------------------------------ |
  | `partner` | واحد من `ADE`، `SONELGAZ`، `SEAAL`، `AADL`، `Algérie Télécom`                        |
  | `account` | معرّف واحد بالضبط — انظر [الخطوة 1](/ar/bill-payment-guides/1-partners-and-accounts) |
  | `ref`     | مرجعك الخاص، 100 حرف كحد أقصى، فريد لكل مُصدِر فاتورة                                |

  ## اختيار `ref`

  `ref` هو ما يجعل الاستكشاف آمناً لإعادة المحاولة. فإذا ضاعت الاستجابة، تبحث عن `ref` بدلاً من إرسال استكشاف ثانٍ — لذا فإن `ref` لا تستطيع إعادة بنائه من بياناتك الخاصة هو معاملة لا تستطيع استعادتها.

  **وصفة عملية:** بادئة ثابتة، ثم معرّف طلبك أو فاتورتك، ولا شيء غير ذلك.

  ```javascript theme={null}
  // Deterministic: the same order always produces the same ref.
  const discoveryRef = `disc-${order.id}`;
  const paymentRef = `pay-${order.id}`;
  ```

  | افعل                                       | لا تفعل                                           |
  | ------------------------------------------ | ------------------------------------------------- |
  | `disc-inv-2026-0042` — مشتق من رقم فاتورتك | `1693476000000` — طابع زمني لا يمكنك إعادة إنتاجه |
  | `disc-order-88213` — مشتق من معرّف طلبك    | `abc123` — سلسلة عشوائية لم تخزّنها               |
  | أبقِ مرجع الاستكشاف ومرجع الدفع متمايزين   | إعادة استخدام `ref` الاستكشاف في الدفع            |

  <Note>
    الـ `ref` فريد لكل مُصدِر فاتورة، لا على المستوى العام. فـ `disc-inv-2026-0042` لدى `ADE` والسلسلة نفسها لدى `SONELGAZ` مرجعان مختلفان. وتمرير `partner` عند البحث عن أحدهما يزيل أي التباس.
  </Note>

  ## إرسال الاستكشاف

  <CodeGroup>
    ```bash cURL theme={null}
    curl https://billapi.oneclickdz.com/v3/bills/discover \
      -X POST \
      -H "Content-Type: application/json" \
      -H "X-Access-Token: YOUR_API_KEY" \
      -d '{
        "partner": "ADE",
        "account": { "reference": "0123456789012345678901234" },
        "ref": "disc-inv-2026-0042"
      }'
    ```

    ```javascript Node.js theme={null}
    const BASE = "https://billapi.oneclickdz.com";
    const KEY = process.env.ONECLICKDZ_API_KEY;

    async function startDiscovery(partner, account, ref) {
      const response = await fetch(`${BASE}/v3/bills/discover`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Access-Token": KEY,
        },
        body: JSON.stringify({ partner, account, ref }),
      });

      const body = await response.json();

      if (!body.success) {
        throw new Error(`${body.error.code}: ${body.error.message}`);
      }

      // PENDING — the bills are not here yet.
      return body.data.transactionId;
    }

    const transactionId = await startDiscovery(
      "ADE",
      { reference: "0123456789012345678901234" },
      "disc-inv-2026-0042",
    );
    ```

    ```python Python theme={null}
    import os
    import requests

    BASE = 'https://billapi.oneclickdz.com'
    KEY = os.getenv('ONECLICKDZ_API_KEY')


    def start_discovery(partner, account, ref):
        response = requests.post(
            f'{BASE}/v3/bills/discover',
            headers={
                'Content-Type': 'application/json',
                'X-Access-Token': KEY
            },
            json={'partner': partner, 'account': account, 'ref': ref}
        )

        body = response.json()

        if not body['success']:
            raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")

        # PENDING — the bills are not here yet.
        return body['data']['transactionId']


    transaction_id = start_discovery(
        'ADE',
        {'reference': '0123456789012345678901234'},
        'disc-inv-2026-0042'
    )
    ```

    ```php PHP theme={null}
    <?php
    const BASE = 'https://billapi.oneclickdz.com';

    function startDiscovery(string $partner, array $account, string $ref): string
    {
        $ch = curl_init(BASE . '/v3/bills/discover');
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'X-Access-Token: ' . getenv('ONECLICKDZ_API_KEY')
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
            'partner' => $partner,
            'account' => $account,
            'ref'     => $ref
        ]));

        $body = json_decode(curl_exec($ch), true);
        curl_close($ch);

        if (!$body['success']) {
            throw new Exception($body['error']['code'] . ': ' . $body['error']['message']);
        }

        // PENDING — the bills are not here yet.
        return $body['data']['transactionId'];
    }

    $transactionId = startDiscovery(
        'ADE',
        ['reference' => '0123456789012345678901234'],
        'disc-inv-2026-0042'
    );
    ?>
    ```
  </CodeGroup>

  ```json theme={null}
  {
    "success": true,
    "data": {
      "transactionId": "68b2f4c1a7d3e9f204c81a55",
      "ref": "disc-inv-2026-0042",
      "status": "PENDING"
    },
    "meta": { "timestamp": "2026-08-31T10:15:32.194Z" },
    "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
  }
  ```

  ## التتبع حتى `READY`

  اقرأ المعاملة حتى تغادر `status` الخاصة بها الحالة `PENDING`. يُحسم الاستكشاف عادةً خلال ثوانٍ قليلة.

  <CodeGroup>
    ```bash cURL theme={null}
    curl https://billapi.oneclickdz.com/v3/bills/transactions/68b2f4c1a7d3e9f204c81a55 \
      -H "X-Access-Token: YOUR_API_KEY"
    ```

    ```javascript Node.js theme={null}
    const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

    async function waitForBills(transactionId, { timeoutMs = 90_000 } = {}) {
      const deadline = Date.now() + timeoutMs;
      let interval = 2000;

      while (Date.now() < deadline) {
        const response = await fetch(
          `${BASE}/v3/bills/transactions/${transactionId}`,
          { headers: { "X-Access-Token": KEY } },
        );

        const body = await response.json();
        if (!body.success) throw new Error(body.error.code);

        const transaction = body.data;

        if (transaction.status === "READY") return transaction.bills;
        if (transaction.status === "FAILED") {
          throw new Error(transaction.error?.code ?? "FAILED");
        }

        await sleep(interval);
        interval = Math.min(interval * 1.5, 10_000);
      }

      throw new Error("Discovery did not finish in time");
    }

    const bills = await waitForBills(transactionId);
    ```

    ```python Python theme={null}
    import time


    def wait_for_bills(transaction_id, timeout_s=90):
        deadline = time.monotonic() + timeout_s
        interval = 2.0

        while time.monotonic() < deadline:
            response = requests.get(
                f'{BASE}/v3/bills/transactions/{transaction_id}',
                headers={'X-Access-Token': KEY}
            )

            body = response.json()
            if not body['success']:
                raise RuntimeError(body['error']['code'])

            transaction = body['data']

            if transaction['status'] == 'READY':
                return transaction.get('bills', [])
            if transaction['status'] == 'FAILED':
                raise RuntimeError(transaction.get('error', {}).get('code', 'FAILED'))

            time.sleep(interval)
            interval = min(interval * 1.5, 10.0)

        raise TimeoutError('Discovery did not finish in time')


    bills = wait_for_bills(transaction_id)
    ```

    ```php PHP theme={null}
    <?php
    function waitForBills(string $transactionId, int $timeoutSeconds = 90): array
    {
        $deadline = time() + $timeoutSeconds;
        $interval = 2;

        while (time() < $deadline) {
            $ch = curl_init(BASE . "/v3/bills/transactions/$transactionId");
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'X-Access-Token: ' . getenv('ONECLICKDZ_API_KEY')
            ]);

            $body = json_decode(curl_exec($ch), true);
            curl_close($ch);

            if (!$body['success']) {
                throw new Exception($body['error']['code']);
            }

            $transaction = $body['data'];

            if ($transaction['status'] === 'READY') {
                return $transaction['bills'] ?? [];
            }
            if ($transaction['status'] === 'FAILED') {
                throw new Exception($transaction['error']['code'] ?? 'FAILED');
            }

            sleep($interval);
            $interval = min((int) ceil($interval * 1.5), 10);
        }

        throw new Exception('Discovery did not finish in time');
    }

    $bills = waitForBills($transactionId);
    ?>
    ```
  </CodeGroup>

  ## قراءة `bills[]`

  المعاملة في حالة `READY` تحمل الفواتير القابلة للدفع في الوقت الحالي.

  ```json theme={null}
  {
    "success": true,
    "data": {
      "transactionId": "68b2f4c1a7d3e9f204c81a55",
      "ref": "disc-inv-2026-0042",
      "type": "discovery",
      "status": "READY",
      "partner": "ADE",
      "account": { "reference": "0123456789012345678901234" },
      "bills": [
        {
          "billId": "sbx_bill_68b2f4c1a7d3e9f204c81a55_0",
          "amount": 443.39,
          "fee": 30.00,
          "label": "Facture ADE"
        }
      ],
      "currency": "DZD",
      "createdAt": "2026-08-31T10:15:32.194Z",
      "updatedAt": "2026-08-31T10:15:33.008Z",
      "completedAt": "2026-08-31T10:15:33.008Z"
    },
    "meta": { "timestamp": "2026-08-31T10:15:34.120Z" },
    "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
  }
  ```

  | الحقل    | المعنى                                             |
  | -------- | -------------------------------------------------- |
  | `billId` | ما ترسله إلى `pay`. انسخه — ولا تُنشئه أبداً بنفسك |
  | `amount` | ما هو مستحق لمُصدِر الفاتورة، بالـ DZD             |
  | `fee`    | رسوم الخدمة مقابل دفع هذه الفاتورة، بالـ DZD       |
  | `label`  | وصف مقروء للبشر، عندما يوفّره مُصدِر الفاتورة      |
  | `period` | فترة الفوترة، عندما يوفّرها مُصدِر الفاتورة        |

  اعرض على العميل `amount + fee`. فهذا المجموع هو ما سيُخصم، وهو يُعاد باسم `total` على المعاملة بمجرد اختيار فاتورة.

  <Note>
    `bills[]` موجودة **فقط** ما دامت `status` هي `READY`. وبمجرد أن تبدأ عملية دفع، تحمل المعاملة `selectedBill` بدلاً منها. اقرأ الفواتير ما دامت بين يديك.
  </Note>

  ## عندما تكون `bills[]` فارغة

  المصفوفة الفارغة نتيجة طبيعية وناجحة — وليست خطأً.

  ```json theme={null}
  {
    "status": "READY",
    "bills": []
  }
  ```

  وهي تعني أحد أمرين، وAPI لا تفرّق بينهما:

  * أن الحساب لا يدين بشيء، أو
  * أن كل ما يدين به الحساب يقع دون **عتبة الاستكشاف البالغة 200 DZD**.

  الفواتير التي تقل عن 200 DZD تُستبعد أثناء الاستكشاف ولا تظهر أبداً.

  <Warning>
    صُغ هذا بعناية. عبارة "لا توجد فواتير قابلة للدفع الآن" دقيقة. أما "أنت لا تدين بشيء" فليست كذلك — فقد توجد فاتورة بقيمة 150 DZD لكنها غير قابلة للدفع عبر هذه API.
  </Warning>

  ```javascript theme={null}
  if (bills.length === 0) {
    return {
      message: "No bills are payable for this account right now.",
      // Not: "This account has no outstanding balance."
    };
  }
  ```

  ## استعادة استجابة ضائعة

  إذا فشل طلب الاستكشاف بطريقة لا تستطيع تفسيرها — انتهاء مهلة، أو انهيار، أو إعادة نشر — فاسأل عمّا آل إليه ذلك `ref`. ولا ترسل استكشافاً ثانياً أبداً.

  ```javascript theme={null}
  async function discoverSafely(partner, account, ref) {
    try {
      return await startDiscovery(partner, account, ref);
    } catch (error) {
      const existing = await findByRef(ref, partner);
      if (existing) return existing.transactionId;
      throw error;
    }
  }

  async function findByRef(ref, partner) {
    const url = new URL(`${BASE}/v3/bills/transactions/by-ref`);
    url.searchParams.set("ref", ref);
    url.searchParams.set("partner", partner);

    const response = await fetch(url, { headers: { "X-Access-Token": KEY } });
    const body = await response.json();

    if (body.success) return body.data;
    if (body.error.code === "NOT_FOUND") return null; // Never landed — safe to resend.
    throw new Error(body.error.code);
  }
  ```

  والبحث نفسه هو الجواب على `403 DUPLICATED_REF`. فهذا الخطأ يعني أن الاستكشاف موجود بالفعل؛ وهو ليس أبداً سبباً لإعادة المحاولة بـ `ref` مختلف، إذ سيبدأ ذلك استكشافاً ثانياً للحساب نفسه.

  ## الأخطاء التي ستصادفها

  | الخطأ                 | HTTP | ما معناه                                      | ما العمل                                       |
  | --------------------- | ---- | --------------------------------------------- | ---------------------------------------------- |
  | `ERR_VALIDATION`      | 400  | الجسم لا يطابق المخطط                         | صحّح الطلب؛ ولا تُعِد المحاولة دون تغيير أبداً |
  | `INVALID_ACCOUNT`     | 400  | المعرّف غير قابل للاستخدام مع هذا المُصدِر    | اطلب من العميل التحقق من فاتورته               |
  | `DUPLICATED_REF`      | 403  | هذا الـ `ref` موجود بالفعل لدى هذا المُصدِر   | ابحث عنه؛ ولا تُعِد الإرسال                    |
  | `BILL_ALREADY_PAID`   | 409  | هذا الحساب دُفع له مؤخراً بالفعل              | ابحث عن المعاملة المدفوعة واعرض إيصالها        |
  | `PAYMENT_IN_PROGRESS` | 409  | هناك دفعة أخرى جارية لهذا الحساب              | انتظر حتى تنتهي                                |
  | `PARTNER_UNAVAILABLE` | 503  | مُصدِر الفاتورة غير قابل للوصول أو مُعطَّل    | أعد المحاولة لاحقاً؛ لم يُنشأ أي شيء           |
  | `AUTH_UNAVAILABLE`    | 503  | لم نتمكن من التحقق من مفتاحك في الوقت المناسب | احترم `Retry-After` وأعد إرسال الطلب نفسه      |
  | `SERVICE_UNAVAILABLE` | 503  | صيانة مُخطَّطة                                | احترم `Retry-After` وأعد المحاولة              |

  الاستكشاف الذي حالته `FAILED` يحمل سببه في `error.code`: إما `INVALID_ACCOUNT` أو `PARTNER_UNAVAILABLE` أو `BILL_ALREADY_PAID` أو `PAYMENT_DECLINED`.

  [كل الرموز، مع أمثلة الأجسام ←](/ar/api-reference/error-handling)

  ## أفضل الممارسات

  <CardGroup cols={2}>
    <Card title="اشتقّ الـ ref" icon="fingerprint">
      ابنِه من معرّف طلبك الخاص لتتمكن دائماً من إعادة بنائه بعد أي فشل.
    </Card>

    <Card title="تابع، ولا تُعِد الإرسال" icon="arrows-rotate">
      الاستكشاف البطيء ليس استكشافاً ضائعاً. اقرأ المعاملة بدلاً من إرسال طلب آخر.
    </Card>

    <Card title="لا تخزّن شيئاً عن الفواتير في الكاش" icon="clock">
      الاستكشاف لقطة لحظية. فإذا انتظر العميل، أعد الاستكشاف بدلاً من الدفع بناءً على أرقام قديمة.
    </Card>

    <Card title="قل قابلة للدفع، لا مستحقة" icon="quote-left">
      `bills[]` فارغة تعني أنه لا شيء قابل للدفع. ولا تعني أن الحساب لا يدين بشيء.
    </Card>
  </CardGroup>

  ## الخطوة التالية

  <Card title="الخطوة 3: دفع الفواتير" icon="money-bill-transfer" href="/ar/bill-payment-guides/3-paying-bills">
    اختر فاتورة، وأكّد المجموع، وأرسل الدفعة بأمان
  </Card>

  ## صفحات ذات صلة

  <CardGroup cols={2}>
    <Card title="استكشاف الفواتير" icon="magnifying-glass-dollar" href="/ar/api-reference/bill-payment/discover-bills">
      مرجع الـ endpoint
    </Card>

    <Card title="جلب معاملة بالمعرّف" icon="id-card" href="/ar/api-reference/bill-payment/check-by-id">
      كائن المعاملة بالكامل
    </Card>

    <Card title="جلب معاملة بالمرجع" icon="tag" href="/ar/api-reference/bill-payment/check-by-ref">
      مسار التعافي
    </Card>

    <Card title="الشركاء والحسابات" icon="address-card" href="/ar/bill-payment-guides/1-partners-and-accounts">
      قواعد المعرّف لكل مُصدِر فاتورة
    </Card>
  </CardGroup>
</div>
