> ## 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.

# اختبار Sandbox

> أعد إنتاج كل نتيجة عند الطلب، ثم انتقل إلى الإنتاج بثقة

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

  الـ sandbox هو المكان الذي تثبت فيه أن تكاملك يتعامل مع الرفض والاسترداد والدفعة غير المؤكدة — وهي نتائج لا يمكنك إنتاجها عند الطلب بأموال حقيقية.

  معرّف الحساب الذي ترسله هو الذي **يختار النتيجة**. وكل سيناريو أدناه حتمي: المعرّف نفسه يُنتج دائماً النتيجة نفسها.

  <Note>
    يستخدم الـ sandbox **نفس المضيف، ونفس المسارات، ونفس ترويسة المفتاح** التي يستخدمها الإنتاج. الشيء الوحيد الذي يتغيّر هو المفتاح. استدعِ [التحقق من مفتاح API](/ar/api-reference/bill-payment/validate-key) واقرأ `key.type` لتأكيد البيئة التي أنت فيها.
  </Note>

  ## ما الذي يبقى كما هو

  كل ما يهم كودك:

  * الـ base URL، وهو `https://billapi.oneclickdz.com`
  * ترويسة `X-Access-Token`
  * المسارات الثمانية جميعها
  * غلاف الاستجابة و`requestId` وترويسة `X-Request-Id`
  * الحالات السبع ودورة الحياة غير المتزامنة
  * الاستطلاع، وعدم التكرار عبر `ref`، وحمايتا `403` / `409`
  * أكواد الأخطاء وحالات HTTP المقابلة لها

  الانتقال إلى الإنتاج يغيّر مفتاحك. ولا يغيّر سطراً واحداً من تكاملك.

  ## ما الذي يختلف

  * طلب الـ sandbox لا يصل أبداً إلى جهة مُصدِرة للفاتورة ولا يحرّك أي مال.
  * تُختار النتائج بمعرّف الحساب، لا بما يدين به الحساب فعلاً.
  * يُعيد `GET /v3/partners` خريطة ثابتة بدلاً من التوفّر الحيّ.
  * تُعاد فواتير الـ sandbox بـ `fee: 0`، فيساوي `total` قيمة `amount`. اقرأ `fee` و`total` من الاستجابة — ففي الإنتاج لن يكونا صفراً.
  * الانتقالات سريعة: يُحسم الاستكشاف في أقل من ثانية بكثير، والدفع في نحو نصف ثانية. أما سيناريو `UNKNOWN` فيبقى عمداً نحو 60 ثانية كي تتمكن من اختبار مسار المراجعة لديك.

  <Warning>
    الـ sandbox ليس اختبار حمل ولا اختبار توفّر. فالجهة المُصدِرة للفاتورة التي تكون `ACTIVE` في خريطة الـ sandbox قد تكون معطّلة في الإنتاج — تعامل مع `503 PARTNER_UNAVAILABLE` بغضّ النظر عمّا أخبرك به الـ sandbox.
  </Warning>

  ## سيناريوهات سير العمل التجاري

  أرسل المعرّف داخل كائن `account` للشريك المذكور. "الاستكشاف" هو ما تصل إليه المعاملة بعد `POST /v3/bills/discover`؛ و"الدفع" هو ما تصل إليه بعد `POST /v3/bills/pay`.

  | السيناريو                                         | الشريك               | معرّف الحساب                             | الاستكشاف                                   | الدفع                                  |
  | ------------------------------------------------- | -------------------- | ---------------------------------------- | ------------------------------------------- | -------------------------------------- |
  | المسار السعيد — فاتورة قابلة للدفع                | `ADE`                | `reference: "0123456789012345678901234"` | `READY`، فاتورة واحدة بقيمة 443.39 DZD      | `SUCCESS`                              |
  | المسار السعيد — فواتير متعددة                     | `SONELGAZ`           | `sonelgaz.invoice_number: "9876543210"`  | `READY`، فاتورتان بقيمة 1200.00 و850.00 DZD | `SUCCESS`                              |
  | عُثر على فواتير (قابلة للدفع)                     | `ADE`                | `reference: "0123456789012340000000001"` | `READY`، فاتورة واحدة بقيمة 320.00 DZD      | `SUCCESS`                              |
  | لا فواتير مستحقة                                  | `ADE`                | `reference: "0123456789012340000000002"` | `READY`، `bills: []`                        | لا شيء للدفع — `404 NOT_FOUND`         |
  | لا فواتير مستحقة                                  | `SEAAL`              | `reference: "0123456789012340000000000"` | `503 PARTNER_UNAVAILABLE` — انظر أدناه      | —                                      |
  | تحت الحد الأدنى 200 DZD (استُبعدت كلها)           | `SONELGAZ`           | `sonelgaz.invoice_number: "0000000003"`  | `READY`، `bills: []`                        | لا شيء للدفع — `404 NOT_FOUND`         |
  | تحت الحد الأدنى 200 DZD (استُبعدت فاتورة 150 DZD) | `ADE`                | `reference: "0123456789012341111111111"` | `READY`، `bills: []`                        | لا شيء للدفع — `404 NOT_FOUND`         |
  | رُفض الدفع (دون خصم)                              | `SONELGAZ`           | `sonelgaz.invoice_number: "4004004004"`  | `READY`، فاتورة واحدة بقيمة 500.00 DZD      | `FAILED` + `PAYMENT_DECLINED`          |
  | رُفض الدفع                                        | `ADE`                | `reference: "0123456789012340000000004"` | `READY`، فاتورة واحدة بقيمة 400.00 DZD      | `FAILED` + `PAYMENT_DECLINED`          |
  | رفض بعد الخصم — استرداد                           | `ADE`                | `ade.sub_id: "000123456789"`             | `READY`، فاتورة واحدة بقيمة 600.00 DZD      | `REFUNDED` + `PAYMENT_DECLINED`        |
  | فشل متأخر — استرداد                               | `ADE`                | `reference: "0123456789012340000000005"` | `READY`، فاتورة واحدة بقيمة 550.00 DZD      | `REFUNDED` + `PAYMENT_DECLINED`        |
  | نتيجة غير مؤكدة — مراجعة ثم استرداد               | `SONELGAZ`           | `sonelgaz.invoice_number: "6006006006"`  | `READY`، فاتورة واحدة بقيمة 900.00 DZD      | `UNKNOWN` لنحو 60 ثانية، ثم `REFUNDED` |
  | حساب غير صالح (تنسيق خاطئ)                        | `ADE`                | `reference: "abc0000000000000000000000"` | `400 INVALID_ACCOUNT`                       | —                                      |
  | حساب غير صالح (تنسيق سليم لكنه غير موجود)         | `SEAAL`              | `reference: "0123456789012340000000009"` | `503 PARTNER_UNAVAILABLE` — انظر أدناه      | —                                      |
  | تعذّر الوصول إلى الجهة المُصدِرة / انتهاء المهلة  | `ADE`                | `reference: "0123456789012345005005005"` | `503 PARTNER_UNAVAILABLE`                   | —                                      |
  | الجهة المُصدِرة غير متاحة                         | `AADL`               | `aadlNumber: "1112223334"`               | `503 PARTNER_UNAVAILABLE`                   | —                                      |
  | مدفوعة مسبقاً (حماية الـ 24 ساعة)                 | `ADE`                | `reference: "0123456789012347777777777"` | `409 BILL_ALREADY_PAID`                     | —                                      |
  | مدفوعة مسبقاً                                     | `ADE`                | `reference: "0123456789012340000000006"` | `409 BILL_ALREADY_PAID`                     | —                                      |
  | معرّف مُعاد استخدامه                              | `Algérie Télécom`    | `phoneNumber: "023456789"`               | `READY`، فاتورة واحدة بقيمة 300.00 DZD      | `SUCCESS`                              |
  | أي معرّف آخر بتنسيق سليم                          | أي جهة مُصدِرة متاحة | أي شيء غير مذكور أعلاه                   | `READY`، فاتورة واحدة بقيمة 500.00 DZD      | `SUCCESS`                              |

  <Note>
    **صفّا SEAAL وصفّ AADL.** كل من `SEAAL` و`AADL` معطّل حالياً في البيئتين، ويجري هذا الفحص قبل اختيار سيناريو الـ sandbox — لذا تُجيب هذه المعرّفات الثلاثة بـ `503 PARTNER_UNAVAILABLE` بدلاً من النتيجة التي يصفها سيناريو كل منها. وهي مذكورة هنا لأنها تصبح قابلة للوصول لحظة إعادة تشغيل هاتين الجهتين. ولاختبار "لا فواتير مستحقة" و"حساب غير صالح" اليوم، استخدم صفوف `ADE`.
  </Note>

  الصيغ الكاملة للكائنات، للنسخ:

  ```json theme={null}
  {
    "partner": "SONELGAZ",
    "account": {
      "sonelgaz": {
        "invoice_number": "9876543210",
        "amount_without_stamp": "15000",
        "ebb_key": "ABC123"
      }
    },
    "ref": "disc-sbx-multi-001"
  }
  ```

  ```json theme={null}
  {
    "partner": "ADE",
    "account": {
      "ade": {
        "sub_id": "000123456789",
        "period": "07/2026",
        "amount": "12000",
        "pay_key": "1234567"
      }
    },
    "ref": "disc-sbx-refund-001"
  }
  ```

  ## سيناريوهات المصادقة والتحكّم

  | المُشغِّل                                    | الاستجابة                  |
  | -------------------------------------------- | -------------------------- |
  | حذف ترويسة `X-Access-Token`                  | `401 MISSING_ACCESS_TOKEN` |
  | إرسال مفتاح غير معروف                        | `401 INVALID_ACCESS_TOKEN` |
  | إعادة استخدام `ref` مع الجهة المُصدِرة نفسها | `403 DUPLICATED_REF`       |

  <Warning>
    نفّذ الثلاثة جميعاً. ومسار `403 DUPLICATED_REF` تحديداً هو ما يعتمد عليه منطق التعافي لديك — فإذا فاجأت إعادة استخدام `ref` كودك في الـ sandbox، فستفاجئه في الإنتاج مع وجود أموال حقيقية.
  </Warning>

  ## تشغيل كامل من البداية إلى النهاية في الـ sandbox

  المسار السعيد لـ `ADE`، من الاستكشاف إلى الإيصال. كل قيمة أدناه حقيقية وقابلة لإعادة الإنتاج.

  <CodeGroup>
    ```bash cURL theme={null}
    BASE="https://billapi.oneclickdz.com"
    KEY="YOUR_SANDBOX_API_KEY"

    # 0. Confirm the environment
    curl -s "$BASE/v3/validate" -H "X-Access-Token: $KEY"

    # 1. Discover
    TXN=$(curl -s "$BASE/v3/bills/discover" \
      -X POST \
      -H "Content-Type: application/json" \
      -H "X-Access-Token: $KEY" \
      -d '{
        "partner": "ADE",
        "account": { "reference": "0123456789012345678901234" },
        "ref": "disc-sbx-001"
      }' | grep -o '"transactionId":"[a-f0-9]*"' | cut -d'"' -f4)

    echo "transaction: $TXN"
    sleep 2

    # 2. Read the bills
    curl -s "$BASE/v3/bills/transactions/$TXN" -H "X-Access-Token: $KEY"

    # 3. Pay the first bill (note the NEW ref)
    curl -s "$BASE/v3/bills/pay" \
      -X POST \
      -H "Content-Type: application/json" \
      -H "X-Access-Token: $KEY" \
      -d "{
        \"transactionId\": \"$TXN\",
        \"billId\": \"sbx_bill_${TXN}_0\",
        \"ref\": \"pay-sbx-001\"
      }"

    sleep 3

    # 4. Confirm the outcome
    curl -s "$BASE/v3/bills/transactions/$TXN" -H "X-Access-Token: $KEY"

    # 5. Download the receipt
    curl -s "$BASE/v3/bills/transactions/$TXN/receipt" \
      -H "X-Access-Token: $KEY" \
      --fail --remote-header-name --remote-name
    ```

    ```javascript Node.js theme={null}
    import { writeFile } from "node:fs/promises";

    const BASE = "https://billapi.oneclickdz.com";
    const KEY = process.env.ONECLICKDZ_SANDBOX_API_KEY;
    const headers = { "X-Access-Token": KEY, "Content-Type": "application/json" };
    const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

    async function call(path, options = {}) {
      const response = await fetch(`${BASE}${path}`, { headers, ...options });
      const body = await response.json();
      if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
      return body;
    }

    async function waitFor(transactionId, predicate, timeoutMs = 120_000) {
      const deadline = Date.now() + timeoutMs;
      while (Date.now() < deadline) {
        const { data } = await call(`/v3/bills/transactions/${transactionId}`);
        if (predicate(data)) return data;
        await sleep(2000);
      }
      throw new Error("Timed out");
    }

    // 0. Confirm the environment
    const { data: identity } = await call("/v3/validate");
    if (identity.key.type !== "SANDBOX") throw new Error("Not a sandbox key");

    // 1. Discover
    const { data: started } = await call("/v3/bills/discover", {
      method: "POST",
      body: JSON.stringify({
        partner: "ADE",
        account: { reference: "0123456789012345678901234" },
        ref: "disc-sbx-001",
      }),
    });

    // 2. Wait for the bills
    const ready = await waitFor(started.transactionId, (t) => t.status === "READY");
    console.log(ready.bills); // 1 bill @ 443.39 DZD

    // 3. Pay the first one, with a NEW ref
    await call("/v3/bills/pay", {
      method: "POST",
      body: JSON.stringify({
        transactionId: started.transactionId,
        billId: ready.bills[0].billId,
        ref: "pay-sbx-001",
      }),
    });

    // 4. Wait for a final state
    const final = await waitFor(started.transactionId, (t) =>
      ["SUCCESS", "FAILED", "REFUNDED"].includes(t.status),
    );
    console.log(final.status, final.operationId);

    // 5. Download the receipt
    const receipt = await fetch(
      `${BASE}/v3/bills/transactions/${started.transactionId}/receipt`,
      { headers: { "X-Access-Token": KEY } },
    );
    await writeFile("receipt-sbx.bin", Buffer.from(await receipt.arrayBuffer()));
    ```

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

    BASE = 'https://billapi.oneclickdz.com'
    KEY = os.getenv('ONECLICKDZ_SANDBOX_API_KEY')
    HEADERS = {'X-Access-Token': KEY, 'Content-Type': 'application/json'}


    def call(method, path, payload=None):
        response = requests.request(method, f'{BASE}{path}', headers=HEADERS, json=payload)
        body = response.json()
        if not body['success']:
            raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
        return body


    def wait_for(transaction_id, predicate, timeout_s=120):
        deadline = time.monotonic() + timeout_s
        while time.monotonic() < deadline:
            data = call('GET', f'/v3/bills/transactions/{transaction_id}')['data']
            if predicate(data):
                return data
            time.sleep(2)
        raise TimeoutError('Timed out')


    # 0. Confirm the environment
    identity = call('GET', '/v3/validate')['data']
    assert identity['key']['type'] == 'SANDBOX', 'Not a sandbox key'

    # 1. Discover
    started = call('POST', '/v3/bills/discover', {
        'partner': 'ADE',
        'account': {'reference': '0123456789012345678901234'},
        'ref': 'disc-sbx-001'
    })['data']

    # 2. Wait for the bills
    ready = wait_for(started['transactionId'], lambda t: t['status'] == 'READY')
    print(ready['bills'])  # 1 bill @ 443.39 DZD

    # 3. Pay the first one, with a NEW ref
    call('POST', '/v3/bills/pay', {
        'transactionId': started['transactionId'],
        'billId': ready['bills'][0]['billId'],
        'ref': 'pay-sbx-001'
    })

    # 4. Wait for a final state
    final = wait_for(
        started['transactionId'],
        lambda t: t['status'] in ('SUCCESS', 'FAILED', 'REFUNDED')
    )
    print(final['status'], final.get('operationId'))

    # 5. Download the receipt
    receipt = requests.get(
        f"{BASE}/v3/bills/transactions/{started['transactionId']}/receipt",
        headers={'X-Access-Token': KEY}
    )
    with open('receipt-sbx.bin', 'wb') as handle:
        handle.write(receipt.content)
    ```

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

    function call(string $method, string $path, ?array $payload = null): array
    {
        $ch = curl_init(BASE . $path);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'X-Access-Token: ' . getenv('ONECLICKDZ_SANDBOX_API_KEY')
        ]);
        if ($payload !== null) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
        }

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

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

        return $body;
    }

    function waitFor(string $transactionId, callable $predicate, int $timeoutSeconds = 120): array
    {
        $deadline = time() + $timeoutSeconds;
        while (time() < $deadline) {
            $data = call('GET', "/v3/bills/transactions/$transactionId")['data'];
            if ($predicate($data)) {
                return $data;
            }
            sleep(2);
        }
        throw new Exception('Timed out');
    }

    // 0. Confirm the environment
    $identity = call('GET', '/v3/validate')['data'];
    if ($identity['key']['type'] !== 'SANDBOX') {
        throw new Exception('Not a sandbox key');
    }

    // 1. Discover
    $started = call('POST', '/v3/bills/discover', [
        'partner' => 'ADE',
        'account' => ['reference' => '0123456789012345678901234'],
        'ref'     => 'disc-sbx-001'
    ])['data'];

    // 2. Wait for the bills
    $ready = waitFor($started['transactionId'], fn($t) => $t['status'] === 'READY');
    print_r($ready['bills']); // 1 bill @ 443.39 DZD

    // 3. Pay the first one, with a NEW ref
    call('POST', '/v3/bills/pay', [
        'transactionId' => $started['transactionId'],
        'billId'        => $ready['bills'][0]['billId'],
        'ref'           => 'pay-sbx-001'
    ]);

    // 4. Wait for a final state
    $final = waitFor(
        $started['transactionId'],
        fn($t) => in_array($t['status'], ['SUCCESS', 'FAILED', 'REFUNDED'], true)
    );
    echo $final['status'] . ' ' . ($final['operationId'] ?? '');

    // 5. Download the receipt
    $ch = curl_init(BASE . "/v3/bills/transactions/{$started['transactionId']}/receipt");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-Access-Token: ' . getenv('ONECLICKDZ_SANDBOX_API_KEY')
    ]);
    file_put_contents('receipt-sbx.bin', curl_exec($ch));
    curl_close($ch);
    ?>
    ```
  </CodeGroup>

  <Note>
    استخدم `ref` جديداً في كل تشغيل، وإلا فسيُجيب التشغيل الثاني بـ `403 DUPLICATED_REF`. وإضافة عدّاد تشغيل الاختبار الخاص بك كلاحقة للـ ref هي أبسط الطرق.
  </Note>

  ## التحقق الموصى به من التكامل

  ثلاثة فحوص تثبت العقد قبل أن تمضي أبعد:

  <Steps>
    <Step title="تحقّق من شكل /v3/validate">
      `account.id` و`account.status` و`account.currency` و`key.type` كلها موجودة، وقيمة `key.type` هي `SANDBOX`.
    </Step>

    <Step title="تأكّد من خريطة /v3/partners">
      خمسة مفاتيح، لكل منها `status` بقيمة `ACTIVE` أو `UNAVAILABLE`، بما في ذلك `Algérie Télécom` بعلامات التشكيل الفرنسية الموجودة فيه.
    </Step>

    <Step title="نفّذ دورة كاملة واحدة">
      استكشاف ودفع وبحث بالـ `ref` — بما يثبت أن `ref` الخاص بك يعيدك إلى المعاملة التي أنشأتها.
    </Step>
  </Steps>

  ## سيناريوهات تستحق الأتمتة

  إلى جانب المسار السعيد، هذه الأربعة هي التي تكشف الأخطاء الحقيقية:

  | الاختبار     | المعرّف                                            | ما الذي يثبته                                                                       |
  | ------------ | -------------------------------------------------- | ----------------------------------------------------------------------------------- |
  | فواتير فارغة | `ADE` `reference: "0123456789012340000000002"`     | أنك تقول "لا شيء قابل للدفع" لا "لا شيء مستحق"، وأن كودك لا ينهار أمام مصفوفة فارغة |
  | رفض          | `ADE` `reference: "0123456789012340000000004"`     | أنك تحرّر أموال العميل عند `FAILED` ولا تخصم منه أبداً                              |
  | استرداد      | `ADE` `reference: "0123456789012340000000005"`     | أنك تتعامل مع مال تحرّك ثم عاد                                                      |
  | مراجعة       | `SONELGAZ` `sonelgaz.invoice_number: "6006006006"` | أنك تحجز الأموال عند `UNKNOWN` لمدة دقيقة دون استرداد أو إعادة إرسال                |

  <Warning>
    سيناريو المراجعة هو أهم اختبار في هذا الجدول. فهو الطريقة الرخيصة الوحيدة لإثبات أن كودك لا يسترد المبلغ لعميل دُفعت فاتورته فعلاً.
  </Warning>

  ## الانتقال إلى الإنتاج

  <Steps>
    <Step title="بدّل المفتاح ولا تغيّر شيئاً آخر">
      نفس الـ base URL، ونفس الترويسة، ونفس المسارات. قيمة المفتاح وحدها هي التي تتغيّر.
    </Step>

    <Step title="تحقّق من البيئة عند الإقلاع">
      استدعِ `/v3/validate` وأفشِل تسلسل الإقلاع إذا لم تكن قيمة `key.type` هي ما يتوقعه ذلك النشر.

      → [التحقق من مفتاح API](/ar/api-reference/bill-payment/validate-key)
    </Step>

    <Step title="أعد قراءة fee وtotal من الاستجابة">
      يُعيد الـ sandbox القيمة `fee: 0`. أما الإنتاج فلا. وإذا افترض أي جزء من كودك أن الرسوم صفر، فسينكسر هنا.
    </Step>

    <Step title="تأكّد أن مستطلِعك يتعامل مع UNKNOWN">
      في الإنتاج هذه الحالة نادرة ومكلفة إن أخطأت في معالجتها. أثبت وجود هذا الفرع قبل أن تحتاج إليه.
    </Step>

    <Step title="تأكّد أن مهمة المطابقة لديك تعمل">
      ينبغي أن تكون قد عملت على الـ sandbox من قبل دون أن تجد شيئاً. وفي أول يوم إنتاج تكون شبكة الأمان الخاصة بك.

      → [الإيصالات والمطابقة](/ar/bill-payment-guides/5-receipts-and-reconciliation)
    </Step>

    <Step title="احتفظ بمفتاح sandbox">
      كل تغيير مستقبلي يُختبر على هذه السيناريوهات قبل أن يصل إلى الإنتاج.
    </Step>
  </Steps>

  ## قائمة تحقّق الانتقال إلى الإنتاج

  | الفحص                                                                               | لماذا                                                                 |
  | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
  | `/v3/validate` يُعيد `PRODUCTION` في نشر الإنتاج                                    | يثبت أنك أطلقت المفتاح الصحيح                                         |
  | المفاتيح مخزّنة في مخزن أسرار، ولا توجد أبداً في نظام إدارة الإصدارات أو في السجلات | مفتاح إنتاج مسرَّب يحرّك أموالاً حقيقية                               |
  | كل `ref` مشتق من معرّف طلبك الخاص                                                   | التعافي بعد انتهاء المهلة يعتمد عليه                                  |
  | الاستكشاف والدفع يستخدمان قيمتي `ref` مختلفتين                                      | إعادة استخدام القيمة نفسها يُجاب عنها بـ `403 DUPLICATED_REF`         |
  | سجل طلبك يُكتب قبل إرسال الدفع                                                      | هو السبيل الوحيد للعثور على دفعة ضائعة                                |
  | `UNKNOWN` يحجز الأموال ولا يسترد أبداً                                              | أغلى خطأ يمكن ارتكابه على الإطلاق                                     |
  | `403` و`409` يُعالجان بالبحث لا بإعادة المحاولة                                     | إعادة المحاولة للالتفاف على حماية هي ما يجعل العملاء يُخصم منهم مرتين |
  | `fee` و`total` يأتيان من الاستجابة                                                  | الرسوم إعدادات وقابلة للتغيّر                                         |
  | الإيصالات تُنزَّل وتُخزَّن عند النجاح                                               | النزاعات تصل بعد أشهر                                                 |
  | المطابقة اليومية تعمل وتُطلق تنبيهات                                                | تكشف كل ما فات المستطلِع                                              |

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

  <CardGroup cols={2}>
    <Card title="أتمت السيناريوهات الأربعة الصعبة" icon="list-check">
      فواتير فارغة، ورفض، واسترداد، ومراجعة. جميعها حتمية النتيجة، لذا تنتمي إلى مجموعة اختباراتك.
    </Card>

    <Card title="لا تفترض التوفّر انطلاقاً من الـ sandbox" icon="triangle-exclamation">
      خريطة شركاء الـ sandbox ثابتة. أما توفّر الإنتاج فحيّ ومتغيّر.
    </Card>

    <Card title="غيّر الـ ref في كل تشغيل" icon="fingerprint">
      وإلا فسيفشل التشغيل الثاني لمجموعة اختباراتك بسبب `DUPLICATED_REF`.
    </Card>

    <Card title="استمر في الاختبار بعد الانتقال إلى الإنتاج" icon="flask">
      الـ sandbox لا يكلّف شيئاً. شغّل مجموعة الاختبارات مع كل إصدار.
    </Card>
  </CardGroup>

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

  <CardGroup cols={2}>
    <Card title="التحقق من مفتاح API" icon="key" href="/ar/api-reference/bill-payment/validate-key">
      أثبت البيئة التي ينتمي إليها المفتاح
    </Card>

    <Card title="نظرة عامة على دفع الفواتير" icon="file-invoice-dollar" href="/ar/bill-payment-guides/overview">
      الخريطة المكوّنة من خمس خطوات
    </Card>

    <Card title="استطلاع الحالة" icon="arrows-rotate" href="/ar/bill-payment-guides/4-status-polling">
      التعامل مع `UNKNOWN` و`REFUNDED`
    </Card>

    <Card title="معالجة الأخطاء" icon="triangle-exclamation" href="/ar/api-reference/error-handling">
      كل أكواد الأخطاء في مكان واحد
    </Card>
  </CardGroup>
</div>
