مكتبة PHP
استخدم المكتبة الرسمية teyra/teyra-pay-php لقبول المدفوعات والتنقل بين صفحات القوائم وإعادة المحاولة بأمان والتحقق من الـ Webhooks في PHP.
لم تُنشر بعد
مكتبة teyra/teyra-pay-php جاهزة ومُختبرة لكنها غير منشورة على Packagist بعد، لذلك لن يعمل الأمر composer require teyra/teyra-pay-php حتى إصدارها. تواصل مع support@teyra.co للحصول على وصول مبكر. وإلى ذلك الحين استخدم واجهة REST مباشرةً كما في البدء السريع.
teyra/teyra-pay-php هي مكتبة تيرا باي الرسمية لـ PHP. تعمل على PHP 7.4 أو أحدث، وتتبع معيار PSR-4، وتستخدم cURL، ولا تتطلب أي حزم إضافية.
- خدمات للمدفوعات والاستردادات وروابط الدفع والإيداعات ونقاط نهاية الـ Webhooks
- مُكرِّر (iterator) يتنقل بين الصفحات تلقائيًا
- ترويسة
Idempotency-Keyمع كل طلبPOST، تُستخدم نفسها في كل إعادة محاولة - إعادة محاولة تلقائية عند أخطاء الشبكة و
429و5xx - استثناءات بأنواع محددة تحمل رمز الخطأ وحالة HTTP والتفاصيل
- دوال للمبالغ لا تستخدم الأعداد العشرية العائمة (floats)
- التحقق من تواقيع الـ Webhooks
التثبيت
تتطلب PHP 7.4 أو أحدث مع الإضافتين curl وjson.
composer require teyra/teyra-pay-phpالبدء السريع
أنشئ عميلًا بمفتاحك السري، ثم أنشئ دفعة ووجّه العميل إلى checkout_url.
<?php
require __DIR__ . '/vendor/autoload.php';
use Teyra\Money;
use Teyra\TeyraPay;
$teyra = new TeyraPay(getenv('TEYRA_SECRET_KEY')); // sk_test_… or sk_live_…
$payment = $teyra->payments->create([
'amount' => Money::toMinor('12.500', 'KWD'), // 12500
'currency' => 'KWD',
'return_url' => 'https://shop.example.com/thanks',
'reference' => 'order-1042',
'customer' => ['name' => 'Sara Ali', 'email' => 'sara@example.com'],
'metadata' => ['order_id' => '1042'],
]);
header('Location: ' . $payment->checkout_url, true, 303);عندما يعود العميل إلى return_url، تأكّد من النتيجة من خادمك ولا تعتمد على إعادة التوجيه وحدها:
$payment = $teyra->payments->retrieve($_GET['payment_id']);
if ($payment->status === 'succeeded') {
// mark the order as paid
}الاستجابات كائنات Teyra\TeyraObject للقراءة فقط. اقرأ الحقول كخصائص أو مفاتيح ($payment->id و$payment['status'] و$payment->customer->email)، أو استدعِ $payment->toArray().
أبقِ المفتاح السري على خادمك
لا تكشف مفاتيح sk_test_… أو sk_live_… في HTML أو JavaScript أو تطبيقات الجوال. حمّلها من متغير بيئة. راجع المصادقة.
خيارات العميل
$teyra = new TeyraPay(getenv('TEYRA_SECRET_KEY'), [
'base_url' => 'https://api.teyra.co', // default
'timeout_ms' => 30000, // per attempt, default 30 seconds
'max_retries' => 2, // default 2
]);المفتاح هو ما يحدد الوضع: مفاتيح الاختبار تُنشئ كائنات اختبار (livemode: false) والمفاتيح الحية تُنشئ كائنات حية. راجع وضع الاختبار والوضع الحي.
الدوال
| الدالة | نقطة النهاية |
|---|---|
$teyra->payments->create($params) | POST /v1/payments |
$teyra->payments->retrieve($id) | GET /v1/payments/:id |
$teyra->payments->list($params) | GET /v1/payments |
$teyra->refunds->create($paymentId, $params) | POST /v1/payments/:id/refunds |
$teyra->refunds->retrieve($id) | GET /v1/refunds/:id |
$teyra->refunds->list($params) | GET /v1/refunds |
$teyra->paymentLinks->create($params) | POST /v1/payment-links |
$teyra->paymentLinks->retrieve($id) | GET /v1/payment-links/:id |
$teyra->paymentLinks->list($params) | GET /v1/payment-links |
$teyra->paymentLinks->deactivate($id) | POST /v1/payment-links/:id/deactivate |
$teyra->deposits->list($params) | GET /v1/deposits |
$teyra->webhookEndpoints->create($params) | POST /v1/webhook-endpoints |
$teyra->webhookEndpoints->list() | GET /v1/webhook-endpoints |
$teyra->webhookEndpoints->delete($id) | DELETE /v1/webhook-endpoints/:id |
المعاملات والاستجابات مطابقة لما في مرجع الـ API. وتقبل كل دالة خيارات الطلب في آخر معامل: ['idempotency_key' => …, 'timeout_ms' => …, 'max_retries' => …].
// Partial refund
$refund = $teyra->refunds->create('pay_01JB…', ['amount' => 5000, 'reason' => 'requested_by_customer']);
// Shareable payment link
$link = $teyra->paymentLinks->create(['amount' => 5000, 'currency' => 'KWD', 'description' => 'Consultation']);
echo $link->url;ترقيم الصفحات
تعيد list() كائن Teyra\Collection يحوي صفحة واحدة، وforeach عليه يمر على عناصر تلك الصفحة. أما autoPagingIterator() فيمر على كل الصفحات، ولا يطلب الصفحة التالية باستخدام next_cursor إلا عند الوصول إليها، مع الاحتفاظ بعوامل التصفية.
// One page
$page = $teyra->payments->list(['status' => 'succeeded', 'limit' => 50]);
count($page); // items on this page
$page->hasMore(); // bool
$page->getNextCursor(); // ?string
// Every item, across pages
foreach ($teyra->payments->list(['status' => 'succeeded'])->autoPagingIterator() as $payment) {
echo $payment->id, ' ', $payment->amount, PHP_EOL;
}
// Page by page
foreach ($teyra->deposits->list(['limit' => 100])->pages() as $page) {
echo count($page), PHP_EOL;
}راجع ترقيم الصفحات لمعرفة طريقة عمل المؤشرات.
منع التكرار
ترسل المكتبة ترويسة Idempotency-Key مع كل طلب POST. إذا لم تمرّر مفتاحًا، تولّد المكتبة معرّف UUID وتستخدمه نفسه في كل إعادة محاولة للاستدعاء، فلا يمكن أن يُنشئ الطلب المعاد دفعة أو استردادًا ثانيًا.
إذا كانت العملية نفسها قد تُنفَّذ مرة أخرى لاحقًا، مثل مهمة في طابور أو نموذج أُرسل مرتين، فمرّر مفتاحك أنت مشتقًا من رقم الطلب:
$teyra->payments->create($params, ['idempotency_key' => 'order-1042-attempt-1']);
$teyra->refunds->create('pay_01JB…', ['amount' => 5000], ['idempotency_key' => 'refund-order-1042-item-3']);استخدام المفتاح نفسه مع جسم مختلف يرمي IdempotencyException (409 idempotency_conflict). راجع منع التكرار.
الأخطاء وإعادة المحاولة
ترث كل الاستثناءات من Teyra\Exception\TeyraPayException، وفيه getErrorCode() وgetHttpStatus() وgetMessage() وgetDetails().
| الاستثناء | الحالة |
|---|---|
InvalidRequestException | 400 و422: validation_error وrefund_exceeds_amount وpayment_not_refundable |
AuthenticationException | 401 |
PermissionException | 403: merchant_not_active وforbidden |
NotFoundException | 404 |
ConflictException وIdempotencyException | 409 |
RateLimitException | 429 (getRetryAfter() بالثواني) |
ApiException | 5xx |
ApiConnectionException | خطأ في الشبكة أو انتهاء المهلة (بلا حالة HTTP) |
use Teyra\Exception\InvalidRequestException;
use Teyra\Exception\TeyraPayException;
try {
$teyra->refunds->create('pay_01JB…', ['amount' => 999999]);
} catch (InvalidRequestException $e) {
echo $e->getErrorCode(); // e.g. refund_exceeds_amount
print_r($e->getDetails()); // e.g. ['fields' => ['amount' => '…']] on validation_error
} catch (TeyraPayException $e) {
error_log($e->getHttpStatus() . ' ' . $e->getErrorCode() . ' ' . $e->getMessage());
}تُعاد المحاولة تلقائيًا عند أخطاء الشبكة وانتهاء المهلة واستجابات 429 و5xx، حتى max_retries مرة (الافتراضي 2)، مع تأخير متزايد: 0.5 ثانية ثم ثانية ثم ثانيتان وهكذا حتى 8 ثوانٍ. وإذا أرسلت الـ API ترويسة Retry-After تنتظر المكتبة المدة المذكورة فيها. لا تُعاد المحاولة لأخطاء 4xx الأخرى. راجع الأخطاء لكل الرموز.
دوال المبالغ
use Teyra\Money;
Money::toMinor('1.500', 'KWD'); // 1500
Money::toMinor('19.99', 'AED'); // 1999
Money::fromMinor(1500, 'KWD'); // '1.500'
Money::fromMinor(2500, 'SAR'); // '25.00'
Money::exponent('BHD'); // 3
Money::toMinor('1.2345', 'KWD'); // throws: KWD amounts have at most 3 decimal placesمرّر المبالغ كنصوص لتحصل على قيم دقيقة. راجع المبالغ والعملات.
التحقق من الـ Webhooks
تتحقق Webhook::constructEvent($payload, $sigHeader, $secret, $tolerance = 300) من ترويسة Teyra-Signature وتعيد الحدث بعد تحليله. وترمي SignatureVerificationException إذا كانت الترويسة مفقودة، أو كان الطابع الزمني بعيدًا بأكثر من $tolerance ثانية، أو لم يطابق أي توقيع. مرّر الجسم الخام قبل json_decode.
<?php
use Teyra\Exception\SignatureVerificationException;
use Teyra\Webhook;
$payload = file_get_contents('php://input');
$header = $_SERVER['HTTP_TEYRA_SIGNATURE'] ?? null;
try {
$event = Webhook::constructEvent($payload, $header, getenv('TEYRA_WEBHOOK_SECRET'));
} catch (SignatureVerificationException $e) {
http_response_code(400);
exit('Invalid signature');
}
if ($event->type === 'payment.succeeded') {
mark_order_paid($event->data->object->id);
}
http_response_code(200);استثنِ مسار الـ Webhook من حماية CSRF. وتبني Webhook::generateTestHeader($payload, $secret) ترويسة صالحة لاختباراتك. راجع التحقق من تواقيع الـ Webhooks والـ Webhooks.
جرّب الـ API في Postman
تفضّل تجربة الطلبات أولًا؟ استورد مجموعة Postman.