> ## 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">
  ## نظرة عامة

  الدفع هو نصف الكتابة في دفع الفواتير. تأخذ `billId` واحداً من استكشاف في حالة `READY`، وترسله، فتحمل المعاملة نفسها الدفعة حتى تصل إلى حالة نهائية.

  هذا هو الاستدعاء الذي يحرّك المال، لذا فإن ترتيب العمليات هنا يهم أكثر من أي مكان آخر في التكامل: **اكتب سجلك الخاص أولاً، ثم أرسل مرة واحدة، ثم تابع الحالة.**

  <Warning>
    الرمز `200` من `pay` يعني أن الدفعة **قُبلت وهي قيد التنفيذ**. وهو لا يعني أن الفاتورة دُفعت. فالنتيجة لا تظهر إلا في `status` الخاص بالمعاملة.
  </Warning>

  ## اختيار فاتورة

  قد تحمل `bills[]` في معاملة `READY` عدة مدخلات. اختر واحدة — فالدفعة تسدد فاتورة واحدة بالضبط.

  ```json theme={null}
  {
    "status": "READY",
    "bills": [
      {
        "billId": "sbx_bill_68b2f4c1a7d3e9f204c81a55_0",
        "amount": 1200.0,
        "fee": 30.0,
        "label": "Facture SONELGAZ"
      },
      {
        "billId": "sbx_bill_68b2f4c1a7d3e9f204c81a55_1",
        "amount": 850.0,
        "fee": 30.00,
        "label": "Facture SONELGAZ"
      }
    ]
  }
  ```

  لدفع عدة فواتير لحساب واحد، ادفع الأولى، وانتظر حتى تصل إلى حالة نهائية، ثم شغّل استكشافاً جديداً. أما وجود دفعتين قيد التنفيذ للحساب نفسه فيُرفض بـ `409 PAYMENT_IN_PROGRESS`.

  ## ما يدفعه عميلك

  | الحقل    | المعنى                                   |
  | -------- | ---------------------------------------- |
  | `amount` | ما هو مستحق لمُصدِر الفاتورة             |
  | `fee`    | رسوم خدمة OneClickDz مقابل دفعها         |
  | `total`  | `amount + fee` — المبلغ المخصوم من رصيدك |

  الرسوم نسبة مئوية من المبلغ، محصورة بين حد أدنى وحد أقصى، و**تُضبط لكل مُصدِر فاتورة على حدة**:

  | مُصدِر الفاتورة   | النسبة | الحد الأدنى | الحد الأقصى |
  | ----------------- | ------ | ----------- | ----------- |
  | `ADE`             | 0.5%   | 30 DZD      | 60 DZD      |
  | `SONELGAZ`        | 0.5%   | 30 DZD      | 60 DZD      |
  | `Algérie Télécom` | 0.5%   | 10 DZD      | 50 DZD      |

  `SEAAL` و`AADL` غير متاحين حاليًا، لذا لا تُنشر رسوم لهما.

  وبالتفصيل، لـ `ADE` و`SONELGAZ`:

  | مبلغ الفاتورة | 0.5% منه | الرسوم المطبَّقة              | المجموع                                      |
  | ------------- | -------- | ----------------------------- | -------------------------------------------- |
  | 150.00        | 0.75     | —                             | دون عتبة 200 DZD؛ لا تظهر أبداً في `bills[]` |
  | 320.00        | 1.60     | 30.00 (الحد الأدنى)           | 350.00                                       |
  | 443.39        | 2.22     | 30.00 (الحد الأدنى)           | 473.39                                       |
  | 1200.00       | 6.00     | 30.00 (الحد الأدنى)           | 1230.00                                      |
  | 6000.00       | 30.00    | 30.00 (النسبة المئوية أخيرًا) | 6030.00                                      |
  | 15000.00      | 75.00    | 60.00 (الحد الأقصى)           | 15060.00                                     |

  <Note>
    لا تصبح النسبة المئوية مؤثرة إلا في الفواتير الكبيرة. فبالنسبة لـ `ADE` و`SONELGAZ` تُحتسب كل فاتورة حتى 6,000 DZD بالحد الأدنى البالغ 30 DZD، ولا تتجاوز الرسوم 60 DZD أبدًا. أما بالنسبة لـ `Algérie Télécom` فالعتبتان المقابلتان هما 2,000 DZD و10,000 DZD.
  </Note>

  <Warning>
    اقرأ `fee` من الاستجابة. فالنسبة المئوية والحدّان إعدادات، لا ثوابت — والرسوم التي تحسبها بنفسك ستختلف في نهاية المطاف عن الرسوم التي تُخصم منك.
  </Warning>

  ## قبل أن ترسل

  <Steps>
    <Step title="احفظ سجلك الخاص">
      اكتب الطلب — العميل، و`transactionId`، و`billId`، و`amount`، و`fee`، و`total`، والـ `ref` الذي أنت على وشك استخدامه — **قبل** أن يغادر الطلبُ عمليتك. فإذا ضاعت الاستجابة، فهذا السطر هو وسيلتك للعثور على الدفعة مرة أخرى.
    </Step>

    <Step title="أكّد المجموع مع عميلك">
      اعرض `amount` و`fee` كلاً على حدة، ثم `total` الذي ستخصمه. ولا تعرض تقديراً أبداً.
    </Step>

    <Step title="أرسل مرة واحدة">
      استدعاء واحد، بـ `ref` جديد لدى هذا المُصدِر.
    </Step>

    <Step title="تابع حتى حالة نهائية">
      `SUCCESS` أو `FAILED` أو `REFUNDED`.

      → [الخطوة 4: تتبع الحالة](/ar/bill-payment-guides/4-status-polling)
    </Step>
  </Steps>

  ## إرسال الدفعة

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

  ```json theme={null}
  {
    "transactionId": "68b2f4c1a7d3e9f204c81a55",
    "billId": "sbx_bill_68b2f4c1a7d3e9f204c81a55_0",
    "ref": "pay-inv-2026-0042"
  }
  ```

  <Note>
    استخدم `ref` **جديداً**، مختلفاً عن مرجع الاستكشاف. فإعادة استخدام `ref` الاستكشاف هنا تُجيب بـ `403 DUPLICATED_REF`. تحتفظ المعاملة بـ `ref` الاستكشاف الأصلي — وهو ما تُعيده الاستجابة وما يبحث عنه `by-ref`.
  </Note>

  <CodeGroup>
    ```bash cURL theme={null}
    curl https://billapi.oneclickdz.com/v3/bills/pay \
      -X POST \
      -H "Content-Type: application/json" \
      -H "X-Access-Token: YOUR_API_KEY" \
      -d '{
        "transactionId": "68b2f4c1a7d3e9f204c81a55",
        "billId": "sbx_bill_68b2f4c1a7d3e9f204c81a55_0",
        "ref": "pay-inv-2026-0042"
      }'
    ```

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

    async function payBill(order, bill) {
      // 1. Persist before sending — this row is your recovery path.
      await db.payments.insert({
        orderId: order.id,
        transactionId: order.transactionId,
        billId: bill.billId,
        amount: bill.amount,
        fee: bill.fee,
        total: bill.amount + bill.fee,
        discoveryRef: order.discoveryRef,
        paymentRef: `pay-${order.id}`,
        state: "SUBMITTING",
      });

      const response = await fetch(`${BASE}/v3/bills/pay`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Access-Token": KEY,
        },
        body: JSON.stringify({
          transactionId: order.transactionId,
          billId: bill.billId,
          ref: `pay-${order.id}`,
        }),
      });

      const body = await response.json();

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

      // PROCESSING — in flight, not paid.
      await db.payments.update(order.id, { state: "PROCESSING" });
      return body.data.transactionId;
    }
    ```

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

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


    def pay_bill(order, bill):
        # 1. Persist before sending — this row is your recovery path.
        db.payments.insert({
            'order_id': order['id'],
            'transaction_id': order['transaction_id'],
            'bill_id': bill['billId'],
            'amount': bill['amount'],
            'fee': bill['fee'],
            'total': bill['amount'] + bill['fee'],
            'discovery_ref': order['discovery_ref'],
            'payment_ref': f"pay-{order['id']}",
            'state': 'SUBMITTING'
        })

        response = requests.post(
            f'{BASE}/v3/bills/pay',
            headers={
                'Content-Type': 'application/json',
                'X-Access-Token': KEY
            },
            json={
                'transactionId': order['transaction_id'],
                'billId': bill['billId'],
                'ref': f"pay-{order['id']}"
            }
        )

        body = response.json()

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

        # PROCESSING — in flight, not paid.
        db.payments.update(order['id'], {'state': 'PROCESSING'})
        return body['data']['transactionId']
    ```

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

    function payBill(array $order, array $bill): string
    {
        // 1. Persist before sending — this row is your recovery path.
        $db->payments->insert([
            'order_id'       => $order['id'],
            'transaction_id' => $order['transaction_id'],
            'bill_id'        => $bill['billId'],
            'amount'         => $bill['amount'],
            'fee'            => $bill['fee'],
            'total'          => $bill['amount'] + $bill['fee'],
            'discovery_ref'  => $order['discovery_ref'],
            'payment_ref'    => 'pay-' . $order['id'],
            'state'          => 'SUBMITTING'
        ]);

        $ch = curl_init(BASE . '/v3/bills/pay');
        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([
            'transactionId' => $order['transaction_id'],
            'billId'        => $bill['billId'],
            'ref'           => 'pay-' . $order['id']
        ]));

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

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

        // PROCESSING — in flight, not paid.
        $db->payments->update($order['id'], ['state' => 'PROCESSING']);
        return $body['data']['transactionId'];
    }
    ?>
    ```
  </CodeGroup>

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

  ## الحُرّاس الثلاثة

  ثلاث قواعد تمنع تحرّك المال نفسه مرتين. وجميعها تجيب **قبل** أن يُخصم أي شيء.

  | الحارس           | الاستجابة                 | المعنى                                                |
  | ---------------- | ------------------------- | ----------------------------------------------------- |
  | مرجع مكرر        | `403 DUPLICATED_REF`      | هذا الـ `ref` يخص بالفعل معاملة نشطة لدى هذا المُصدِر |
  | مدفوعة بالفعل    | `409 BILL_ALREADY_PAID`   | هذه الفاتورة دُفعت بنجاح بالفعل                       |
  | دفعة قيد التنفيذ | `409 PAYMENT_IN_PROGRESS` | هناك دفعة أخرى لهذه الفاتورة لم تنتهِ بعد             |

  <Warning>
    ولا واحد من هذه الأخطاء سبب لإعادة المحاولة بـ `ref` مختلف. فكل واحد منها يعني أن العمل إما أُنجز بالفعل وإما أنه جارٍ الآن. وإعادة المحاولة للالتفاف حول حارس هي ما يجعل العميل يُخصم منه مرتين.
  </Warning>

  والاستجابة الصحيحة للثلاثة واحدة: اقرأ المعاملة وتابع من حالتها.

  ```javascript theme={null}
  async function payOnce(order, bill) {
    try {
      return await payBill(order, bill);
    } catch (error) {
      const [code] = String(error.message).split(":");

      if (["DUPLICATED_REF", "BILL_ALREADY_PAID", "PAYMENT_IN_PROGRESS"].includes(code)) {
        // Already handled by us or by an earlier attempt — do not send again.
        const transaction = await getTransaction(order.transactionId);
        return transaction.transactionId;
      }

      throw error;
    }
  }
  ```

  ## عندما لا تصل الاستجابة أبداً

  انتهاء المهلة لا يخبرك بشيء عمّا إذا كانت الدفعة قد تمت. اقرأ المعاملة قبل أن تفعل أي شيء آخر.

  ```javascript theme={null}
  async function settleUnknownOutcome(order) {
    const response = await fetch(
      `${BASE}/v3/bills/transactions/${order.transactionId}`,
      { headers: { "X-Access-Token": KEY } },
    );

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

    switch (body.data.status) {
      case "READY":
        return "NOT_SENT"; // The payment never started — safe to send it.
      case "PROCESSING":
      case "UNKNOWN":
        return "IN_FLIGHT"; // Keep polling. Do not resend.
      case "SUCCESS":
      case "FAILED":
      case "REFUNDED":
        return body.data.status; // Already settled.
      default:
        return "IN_FLIGHT";
    }
  }
  ```

  <Warning>
    **لا تُعِد إرسال دفعة أبداً لمجرد أن الطلب انتهت مهلته.** فالمعاملة التي ما زالت في `READY` هي الحالة الوحيدة التي تثبت أن الدفعة لم تبدأ.
  </Warning>

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

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

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

  <CardGroup cols={2}>
    <Card title="اكتب قبل أن ترسل" icon="database">
      احفظ الطلب و`ref` الخاص به أولاً. عندها تكون الاستجابة الضائعة قابلة للاستعادة دائماً.
    </Card>

    <Card title="ref واحد لكل استدعاء" icon="fingerprint">
      `disc-` للاستكشاف، و`pay-` للدفع، وكلاهما مشتق من معرّف طلبك.
    </Card>

    <Card title="اخصم المجموع المُعاد" icon="calculator">
      اخصم من عميلك الـ `total` الذي أنتجته API، لا رقماً حسبته أنت.
    </Card>

    <Card title="تعامل مع الحارس كجواب" icon="shield-halved">
      `403` و`409` يعنيان أن العمل أُنجز أو أنه جارٍ. ابحث عنه بدلاً من الالتفاف حوله.
    </Card>
  </CardGroup>

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

  <Card title="الخطوة 4: تتبع الحالة" icon="arrows-rotate" href="/ar/bill-payment-guides/4-status-polling">
    تابع الدفعة حتى حالة نهائية، وتعامل مع `UNKNOWN` بشكل صحيح
  </Card>

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

  <CardGroup cols={2}>
    <Card title="دفع فاتورة" icon="money-bill-transfer" href="/ar/api-reference/bill-payment/pay-bill">
      مرجع الـ endpoint
    </Card>

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

    <Card title="استكشاف الفواتير" icon="magnifying-glass-dollar" href="/ar/bill-payment-guides/2-discovering-bills">
      من أين يأتي `billId`
    </Card>

    <Card title="الإيصالات والمطابقة" icon="scale-balanced" href="/ar/bill-payment-guides/5-receipts-and-reconciliation">
      ما ينبغي الاحتفاظ به بعد النجاح
    </Card>
  </CardGroup>
</div>
