البداية السريعة
تكامل دفع بالحد الأدنى: إنشاء فاتورة عبر API ومعالجة إشعار الدفع. ويتناول القسم الأخير تسليم الطلب. الأمثلة مكتفية بذاتها؛ ولا تستخدم أي SDK.
تعمل مع وكيل ذكاء اصطناعي؟ أعطه نسخة markdown من هذه الصفحة. فيها تفصيل كافٍ لبناء تكامل لموقعك أو متجرك.
ما تحتاج إليه
| القيمة | من أين تحصل عليها |
|---|---|
| مفتاح API | قسم عمليات التكامل (Integrations) |
| معرّف المشروع | قسم المشاريع (Projects) |
| المفتاح السري للـ 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
- PHP
- TypeScript
- JavaScript
- Python
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
$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
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
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
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
الاستجابة:
{
"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
- TypeScript
- JavaScript
- Python
<?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);
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);
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);
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.
تسليم الطلب
إشعار متحقَّق منه بالحالة paid يؤكد الدفع. خطوات التسليم:
- ابحث عن الطلب عبر
invoice.serviceData— القيمة المُمرَّرة عند إنشاء الفاتورة (order-4172). - تحقق عبر
invoice.idمما إذا كانت هذه الفاتورة قد سُلِّمت من قبل. قد يصل إشعار بالمعرّفinvoice.idنفسه أكثر من مرة — سلّم الطلب مرة واحدة واحفظ علامة المعالجة. - تحقق من المبلغ والعملة مقابل
invoice.amountFiatوinvoice.currencyFiat— وهما قيمتا الفاتورة الأصليتان ولا تتغيران أبدًا. أماamountFiatUSDفتُستبدل بالمبلغ المستلم فعليًا. قارن المبالغ كأرقام لا كسلاسل نصية: تُحذف الأصفار الأخيرة، فتصل49.90بالشكل49.9. وإذا لم تطابق القيم الطلب، وجّه الفاتورة إلى مراجعة يدوية بدل التسليم — هذه الحالات يتناولها قسم ربط الدفعات واختلاف المبالغ. - سلّم الطلب وعلّمه كمعالَج.
لا تتحقق من مهلة الدفع عند التسليم: فقد تكون الدفعة قد تأكدت على السلسلة بعد timeToPayDatetime، ويستطيع التاجر ربط الدفعة بالفاتورة يدويًا. الإشعار نفسه يؤكد الدفع.
ما التالي
- مرجع API — قوائم الفواتير، والإلغاء، والإحصاءات، والأرصدة
- التحقق من توقيع الـ Webhook — آلية التوقيع بالتفصيل
- ربط الدفعات واختلاف المبالغ — دفع ناقص، ودفع زائد، ودفعات غير مرتبطة
- نماذج HTML — قبول المدفوعات بدون API