# البداية السريعة

تكامل دفع بالحد الأدنى: إنشاء فاتورة عبر API ومعالجة إشعار الدفع. ويتناول القسم الأخير تسليم الطلب. الأمثلة مكتفية بذاتها؛ ولا تستخدم أي SDK.

:::tip
**تعمل مع وكيل ذكاء اصطناعي؟** أعطه [نسخة markdown من هذه الصفحة](/ar/quick-start.md). فيها تفصيل كافٍ لبناء تكامل لموقعك أو متجرك.
:::

## ما تحتاج إليه

| القيمة | من أين تحصل عليها |
| --- | --- |
| مفتاح API | قسم [عمليات التكامل (Integrations)](https://dash.bitsby.app/integrations/list) |
| معرّف المشروع | قسم [المشاريع (Projects)](https://dash.bitsby.app/projects/list) |
| المفتاح السري للـ Webhook | إعدادات المشروع |

في إعدادات المشروع نفسها، حدّد **Webhook URL** — عنوان معالج الإشعارات على خادمك. HTTPS فقط؛ ولا تتبع الخدمة عمليات إعادة التوجيه.

تستخدم الأمثلة قيمًا اختبارية — استبدلها بقيمك.

## إنشاء فاتورة

`POST https://api.bitsby.app/invoices/create`، وتمرّر مفتاح API في ترويسة `Authorization`.

| المعطى | إلزامي | القيمة |
| --- | --- | --- |
| `projectId` | نعم | معرّف المشروع، UUID |
| `amountFiat` | نعم | مبلغ الفاتورة |
| `currencyFiat` | نعم | العملة: USD وEUR وRUB |
| `timeToPay` | نعم | مهلة الدفع بالساعات: 0.5 و1 و3 و6 و12 |
| `description` | لا | الوصف، يظهر للمشتري على صفحة الدفع |
| `serviceData` | لا | بيانات خدمية لا تظهر للمشتري. ننصح بتمرير رقم الطلب: فهو يعود في إشعار الدفع |

**cURL**

```bash showLineNumbers
curl -X POST https://api.bitsby.app/invoices/create \
  -H "Authorization: Token MSvL2ltaDZdWVjmZURURMVWhqSJLT2NURjhL2Fla1Z1T1IxQTltKs1T3Ay" \
  -F "projectId=9deea1e2-0c08-41a3-bdc2-a34eada3892d" \
  -F "amountFiat=49.90" \
  -F "currencyFiat=USD" \
  -F "timeToPay=1" \
  -F "description=Order 4172" \
  -F "serviceData=order-4172"
```

**PHP**

```php showLineNumbers
<?php
$apiKey    = 'MSvL2ltaDZdWVjmZURURMVWhqSJLT2NURjhL2Fla1Z1T1IxQTltKs1T3Ay';
$projectId = '9deea1e2-0c08-41a3-bdc2-a34eada3892d';

$ch = curl_init('https://api.bitsby.app/invoices/create');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => ['Authorization: Token '.$apiKey],
    CURLOPT_POSTFIELDS     => http_build_query([
        'projectId'    => $projectId,
        'amountFiat'   => 49.90,
        'currencyFiat' => 'USD',
        'timeToPay'    => 1,
        'description'  => 'Order 4172',
        'serviceData'  => 'order-4172',  // your order id
    ]),
]);

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

if ($response['result'] !== 'success') {
    exit('API error: '.$response['data']);
}

$invoice = $response['data'];

// $invoice['id']  — invoice id, store it with the order
// $invoice['url'] — payment page for the customer
```

**TypeScript**

```typescript showLineNumbers
const API_KEY = 'MSvL2ltaDZdWVjmZURURMVWhqSJLT2NURjhL2Fla1Z1T1IxQTltKs1T3Ay';
const PROJECT_ID = '9deea1e2-0c08-41a3-bdc2-a34eada3892d';

interface Invoice {
    id: string;
    uid: string;
    url: string;
    timeToPayDatetime: string;
}

const response = await fetch('https://api.bitsby.app/invoices/create', {
    method: 'POST',
    headers: { Authorization: `Token ${API_KEY}` },
    body: new URLSearchParams({
        projectId: PROJECT_ID,
        amountFiat: '49.90',
        currencyFiat: 'USD',
        timeToPay: '1',
        description: 'Order 4172',
        serviceData: 'order-4172',  // your order id
    }),
    signal: AbortSignal.timeout(10_000),
});

const result: { result: string; data: Invoice | string } = await response.json();

if (result.result !== 'success') {
    throw new Error(`API error: ${result.data}`);
}

const invoice = result.data as Invoice;

// invoice.id  — invoice id, store it with the order
// invoice.url — payment page for the customer
```

**JavaScript**

```js showLineNumbers
const API_KEY = 'MSvL2ltaDZdWVjmZURURMVWhqSJLT2NURjhL2Fla1Z1T1IxQTltKs1T3Ay';
const PROJECT_ID = '9deea1e2-0c08-41a3-bdc2-a34eada3892d';

const response = await fetch('https://api.bitsby.app/invoices/create', {
    method: 'POST',
    headers: { Authorization: `Token ${API_KEY}` },
    body: new URLSearchParams({
        projectId: PROJECT_ID,
        amountFiat: '49.90',
        currencyFiat: 'USD',
        timeToPay: '1',
        description: 'Order 4172',
        serviceData: 'order-4172',  // your order id
    }),
    signal: AbortSignal.timeout(10_000),
});

const result = await response.json();

if (result.result !== 'success') {
    throw new Error(`API error: ${result.data}`);
}

const invoice = result.data;

// invoice.id  — invoice id, store it with the order
// invoice.url — payment page for the customer
```

**Python**

```python showLineNumbers
import requests

API_KEY = 'MSvL2ltaDZdWVjmZURURMVWhqSJLT2NURjhL2Fla1Z1T1IxQTltKs1T3Ay'
PROJECT_ID = '9deea1e2-0c08-41a3-bdc2-a34eada3892d'

response = requests.post(
    'https://api.bitsby.app/invoices/create',
    headers={'Authorization': f'Token {API_KEY}'},
    data={
        'projectId': PROJECT_ID,
        'amountFiat': '49.90',
        'currencyFiat': 'USD',
        'timeToPay': '1',
        'description': 'Order 4172',
        'serviceData': 'order-4172',  # your order id
    },
    timeout=10,
)

result = response.json()

if result['result'] != 'success':
    raise RuntimeError(f"API error: {result['data']}")

invoice = result['data']

# invoice['id']  — invoice id, store it with the order
# invoice['url'] — payment page for the customer
```

الاستجابة:

```json
{
  "result": "success",
  "data": {
    "id": "ade9550d-3dc7-4fd3-b94e-3b4c12aaaa0c",
    "uid": "MXNj4m8HhcM4",
    "createDatetime": "2026-09-14 10:12:03",
    "timeToPayDatetime": "2026-09-14 11:12:03",
    "commissionFiatUSD": 0.5,
    "amountFiatUSD": 49.9,
    "url": "https://dash.bitsby.app/invoices/pay/MXNj4m8HhcM4"
  }
}
```

احفظ `data.id` مع الطلب وأرسل المشتري إلى `data.url` — صفحة الدفع. أوصل الرابط بأي طريقة تريدها: إعادة توجيه، أو رسالة بريد إلكتروني، أو رسالة من بوت. تبقى الفاتورة صالحة حتى `timeToPayDatetime` (UTC).

## معالجة الإشعار

عندما تنتقل الفاتورة إلى الحالة مدفوعة (Paid)، ترسل الخدمة طلب POST إلى Webhook URL المحدَّد في المشروع. الجسم بصيغة JSON؛ والتوقيع في الترويسات:

| الترويسة | القيمة |
| --- | --- |
| `X-Timestamp` | وقت الإرسال، unix time بالثواني |
| `X-Signature` | `sha256=` + قيمة HMAC-SHA256 بالنظام الست عشري للسلسلة `<timestamp>.<request body>` |

مفتاح التوقيع هو المفتاح السري للـ Webhook. ويُحسب التوقيع على الجسم الخام للطلب، لذلك تحقق منه قبل تحليل JSON.

**PHP**

```php showLineNumbers
<?php
$secret = 'k7QwR2mZ9tXbN4vL8sJpH3dF6yA1cE0u';

$body      = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

// Reject replayed requests: allow up to 5 minutes of clock drift
if (abs(time() - (int)$timestamp) > 300) {
    http_response_code(400);
    exit;
}

$expected = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$body, $secret);

if (!hash_equals($expected, $signature)) {
    http_response_code(403);
    exit;
}

$invoice = json_decode($body, true)['invoice'];

if ($invoice['status'] === 'paid') {
    // $invoice['serviceData'] — the order id passed at creation: 'order-4172'
    // $invoice['amountFiat']  — the original invoice amount: 49.9
    // Issue the order here, see the next section
}

http_response_code(200);
```

**TypeScript**

```typescript showLineNumbers
import { createHmac, timingSafeEqual } from 'node:crypto';
import express, { type Request, type Response } from 'express';

const SECRET = 'k7QwR2mZ9tXbN4vL8sJpH3dF6yA1cE0u';
const app = express();

// express.raw: the signature is computed over the raw request body
app.post('/webhook', express.raw({ type: 'application/json' }), (req: Request, res: Response) => {
    const timestamp = req.get('X-Timestamp') ?? '';
    const signature = req.get('X-Signature') ?? '';
    const body = (req.body as Buffer).toString('utf8');

    // Reject replayed requests: allow up to 5 minutes of clock drift
    if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
        return res.sendStatus(400);
    }

    const expected = 'sha256=' + createHmac('sha256', SECRET)
        .update(`${timestamp}.${body}`, 'utf8')
        .digest('hex');

    const a = Buffer.from(expected);
    const b = Buffer.from(signature);

    if (a.length !== b.length || !timingSafeEqual(a, b)) {
        return res.sendStatus(403);
    }

    const { invoice } = JSON.parse(body);

    if (invoice.status === 'paid') {
        // invoice.serviceData — the order id passed at creation: 'order-4172'
        // invoice.amountFiat  — the original invoice amount: 49.9
        // Issue the order here, see the next section
    }

    res.sendStatus(200);
});

app.listen(8080);
```

**JavaScript**

```js showLineNumbers
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';

const SECRET = 'k7QwR2mZ9tXbN4vL8sJpH3dF6yA1cE0u';
const app = express();

// express.raw: the signature is computed over the raw request body
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
    const timestamp = req.get('X-Timestamp') ?? '';
    const signature = req.get('X-Signature') ?? '';
    const body = req.body.toString('utf8');

    // Reject replayed requests: allow up to 5 minutes of clock drift
    if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
        return res.sendStatus(400);
    }

    const expected = 'sha256=' + createHmac('sha256', SECRET)
        .update(`${timestamp}.${body}`, 'utf8')
        .digest('hex');

    const a = Buffer.from(expected);
    const b = Buffer.from(signature);

    if (a.length !== b.length || !timingSafeEqual(a, b)) {
        return res.sendStatus(403);
    }

    const { invoice } = JSON.parse(body);

    if (invoice.status === 'paid') {
        // invoice.serviceData — the order id passed at creation: 'order-4172'
        // invoice.amountFiat  — the original invoice amount: 49.9
        // Issue the order here, see the next section
    }

    res.sendStatus(200);
});

app.listen(8080);
```

**Python**

```python showLineNumbers
import hashlib
import hmac
import time

from flask import Flask, request

SECRET = 'k7QwR2mZ9tXbN4vL8sJpH3dF6yA1cE0u'
app = Flask(__name__)

@app.post('/webhook')
def webhook():
    timestamp = request.headers.get('X-Timestamp', '')
    signature = request.headers.get('X-Signature', '')
    # The signature is computed over the raw request body
    body = request.get_data(as_text=True)

    # Reject replayed requests: allow up to 5 minutes of clock drift
    if abs(time.time() - int(timestamp or 0)) > 300:
        return '', 400

    expected = 'sha256=' + hmac.new(
        SECRET.encode(),
        f'{timestamp}.{body}'.encode(),
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        return '', 403

    invoice = request.get_json()['invoice']

    if invoice['status'] == 'paid':
        # invoice['serviceData'] — the order id passed at creation: 'order-4172'
        # invoice['amountFiat']  — the original invoice amount: 49.9
        # Issue the order here, see the next section
        pass

    return '', 200
```

أجب برمز 2xx خلال 10 ثوانٍ. أي رمز آخر أو إعادة توجيه أو انتهاء مهلة يُعد تسليمًا فاشلًا: تعيد الخدمة المحاولة على فترات متزايدة من 5 دقائق حتى 24 ساعة، ثم تتوقف. انقل المعالجة الطويلة إلى طابور: أجب بالرمز 200 أولًا، ثم اشتغل على الطلب.

صيغة الإشعار الكاملة ومرجع الحقول في قسم [Webhook URL](./webhook-url/index.md).

## تسليم الطلب

إشعار متحقَّق منه بالحالة `paid` يؤكد الدفع. خطوات التسليم:

1. **ابحث عن الطلب** عبر `invoice.serviceData` — القيمة المُمرَّرة عند إنشاء الفاتورة (`order-4172`).
2. **تحقق عبر `invoice.id` مما إذا كانت هذه الفاتورة قد سُلِّمت من قبل.** قد يصل إشعار بالمعرّف `invoice.id` نفسه أكثر من مرة — سلّم الطلب مرة واحدة واحفظ علامة المعالجة.
3. **تحقق من المبلغ والعملة** مقابل `invoice.amountFiat` و`invoice.currencyFiat` — وهما قيمتا الفاتورة الأصليتان ولا تتغيران أبدًا. أما `amountFiatUSD` فتُستبدل بالمبلغ المستلم فعليًا. قارن المبالغ كأرقام لا كسلاسل نصية: تُحذف الأصفار الأخيرة، فتصل `49.90` بالشكل `49.9`. وإذا لم تطابق القيم الطلب، وجّه الفاتورة إلى مراجعة يدوية بدل التسليم — هذه الحالات يتناولها قسم [ربط الدفعات واختلاف المبالغ](./payment-matching.md).
4. **سلّم الطلب وعلّمه كمعالَج.**

لا تتحقق من مهلة الدفع عند التسليم: فقد تكون الدفعة قد تأكدت على السلسلة بعد `timeToPayDatetime`، ويستطيع التاجر ربط الدفعة بالفاتورة يدويًا. الإشعار نفسه يؤكد الدفع.

## ما التالي

- [مرجع API](./api/index.md) — قوائم الفواتير، والإلغاء، والإحصاءات، والأرصدة
- [التحقق من توقيع الـ Webhook](./webhook-url/signature-verification.md) — آلية التوقيع بالتفصيل
- [ربط الدفعات واختلاف المبالغ](./payment-matching.md) — دفع ناقص، ودفع زائد، ودفعات غير مرتبطة
- [نماذج HTML](./creating-invoices/html-forms/index.md) — قبول المدفوعات بدون API
