إنتقل إلى المحتوى الرئيسي

PHP SDK

استخدم PHP SDK الرسمية من OrcaRail لقبول مدفوعات الكريبتو من أي تطبيق PHP. تتولى الـ SDK مصادقة Basic وتنسيق JSON للطلبات والاستجابات والتحقق من توقيعات الـ webhooks.

يتبع العميل الأسلوب الحديث لـ Stripe PHP: عميل OrcaRailClient واحد، وخصائص خدمات تُنشأ عند أول استخدام، ومصفوفات ترابطية لمعاملات الطلب، وكائنات استجابة متوافقة مع الإصدارات المستقبلية.

المتطلبات​

  • PHP 8.1 أو أحدث
  • ext-curl وext-json

التثبيت​

ثبّت الحزمة باستخدام Composer:

composer require orcarail/orcarail-php

اسم الحزمة orcarail/orcarail-php وهي تحمّل النطاق OrcaRail\ تلقائيًا (PSR-4). للتثبيت من نسخة مستودع بدلًا من ذلك، أضف مستودع path أو vcs إلى composer.json:

{
"repositories": [{ "type": "path", "url": "../orcarail/sdks-php" }],
"require": { "orcarail/orcarail-php": "*" }
}

الإعداد​

أنشئ عميلًا بمفتاح API وسرّه. واختياريًا اضبط رابطًا أساسيًا مخصصًا (مثل التطوير المحلي) ومهل الانتظار.

<?php

require __DIR__ . '/vendor/autoload.php';

use OrcaRail\OrcaRailClient;

$orcarail = new OrcaRailClient([
'api_key' => getenv('ORCARAIL_API_KEY'), // e.g. "ak_live_xxx"
'api_secret' => getenv('ORCARAIL_API_SECRET'), // e.g. "sk_live_xxx"
'base_url' => 'https://api.orcarail.com/api/v1', // optional; default
'timeout' => 30000, // optional; milliseconds, default 30000
'connect_timeout' => 10000, // optional; milliseconds, default 10000
]);

للتطوير المحلي، وجّه base_url إلى API الخاصة بك:

$orcarail = new OrcaRailClient([
'api_key' => getenv('ORCARAIL_API_KEY'),
'api_secret' => getenv('ORCARAIL_API_SECRET'),
'base_url' => 'http://127.0.0.1:3000/api/v1',
'timeout' => 65000, // /complete can take up to ~65s
]);

تُنشأ الخدمات عند أول وصول وتُعاد استخدامها: $orcarail->paymentIntents و$orcarail->subscriptions و$orcarail->pay و$orcarail->rates و$orcarail->products و$orcarail->prices.

كائنات الاستجابة​

يصبح كل كائن JSON في الاستجابة OrcaRail\OrcaRailObject. تُحفظ الحقول غير المعروفة، فتُقرأ حقول API الجديدة دون ترقية الـ SDK. تدعم الكائنات الوصول بالخصائص وبالمصفوفة والتكرار وcount() وtoArray() وjson_encode().

$intent = $orcarail->paymentIntents->retrieve($intentId);

echo $intent->status; // property access
echo $intent['client_secret']; // array access
echo $intent->payment_link->link; // nested object
$asArray = $intent->toArray(); // plain PHP array

تعيد المفاتيح المفقودة null بدلًا من إطلاق تحذير، لذا تحقق قبل الاستخدام:

if ($intent->payment_link !== null) {
header('Location: ' . $intent->payment_link->link);
}

الإنتاج مقابل الـ sandbox​

تبدأ مفاتيح مؤسسة sandbox بـ ak_test_ (شبكات تجريبية، دون أموال حقيقية). تكمل $orcarail->paymentIntents->simulate($id) دفعة sandbox دون محفظة. تحمل أحداث الـ webhook قيمة livemode ($event->livemode === false لأحداث الـ sandbox).

العمولة (التقسيم والحصول على حصتك)​

يمكن أن يكون لمفاتيح API نسبة عمولة اختيارية (0–100). اضبطها في لوحة التحكم ← مفاتيح API أو عبر PATCH /api/v1/api-keys/:id مع commissionPercent. تتضمن المدفوعات المنشأة بذلك المفتاح العمولة في عرض السعر (مرئية للدافع)، ويُرسل مبلغ العمولة إلى عنوان إعدادات السحب لديك عند سحب الأموال.

Payment Intents​

إنشاء Payment Intent​

أنشئ payment intent إما بـ price_id من الكتالوج أو بالمجموعة الكاملة لمعاملات المبلغ المباشر (amount وcurrency وtokenId وnetworkId). الشكلان متنافيان؛ وترفض الـ SDK أي خلط بينهما قبل إرسال الطلب.

$intent = $orcarail->paymentIntents->create([
'amount' => '100.00',
'currency' => 'usd',
'tokenId' => '550e8400-e29b-41d4-a716-446655440000', // token UUID
'networkId' => '3f1a0c1e-9d7b-4f8a-9d21-4a2f6b7c8d90', // network UUID
'return_url' => 'https://yourapp.com/success',
'cancel_url' => 'https://yourapp.com/cancel',
'description' => 'Order #12345',
'metadata' => ['order_id' => '12345'],
// optional: override where funds are withdrawn, keyed by chain family
'withdrawal_addresses' => ['evm' => '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'],
]);

echo $intent->id; // e.g. "550e8400-e29b-41d4-a716-446655440000"
echo $intent->client_secret;
echo $intent->payment_link?->link;

القيمة الافتراضية لـ payment_method_types هي ['crypto'] عند حذفه. استخدم المدفوعات: سرد الشبكات والمدفوعات: سرد الرموز حسب الشبكة للحصول على معرّفات UUID للشبكة والرمز.

مع سعر من الكتالوج، مرّر price_id وحده:

$intent = $orcarail->paymentIntents->create([
'price_id' => $priceId,
'return_url' => 'https://yourapp.com/success',
]);

التأكيد والتوجيه إلى الدفع​

أكّد الـ intent للحصول على رابط صفحة الدفع المستضافة، ثم وجّه العميل. يُصادَق على التأكيد بمفتاح API الخاص بك ويتحقق من الـ client secret.

$confirmed = $orcarail->paymentIntents->confirm($intent->id, [
'client_secret' => $intent->client_secret,
'return_url' => 'https://yourapp.com/success',
]);

$payUrl = $confirmed->pay_url ?? $confirmed->next_action?->redirect_to_url?->url;
header('Location: ' . $payUrl);

استرجاع Payment Intent​

$intent = $orcarail->paymentIntents->retrieve('550e8400-e29b-41d4-a716-446655440000');
echo $intent->status;

تحديث Payment Intent​

حدّث المبلغ أو البيانات الوصفية أو الروابط أو عناوين السحب قبل أن يدفع العميل:

$updated = $orcarail->paymentIntents->update('550e8400-e29b-41d4-a716-446655440000', [
'amount' => '150.00',
'metadata' => ['order_id' => '12345'],
'withdrawal_addresses' => ['evm' => '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'],
]);

إلغاء Payment Intent​

$canceled = $orcarail->paymentIntents->cancel('550e8400-e29b-41d4-a716-446655440000');

إكمال Payment Intent​

عندما يصل العميل إلى رابط النجاح/العودة، علّم الـ intent بأنه قيد المعالجة (يطلق ذلك الـ webhook payment_intent.processing):

$completed = $orcarail->paymentIntents->complete('550e8400-e29b-41d4-a716-446655440000');

الدفع حسب الـ slug​

إذا كنت تستخدم روابط دفع بـ slug، فاقرأ تفاصيل الدفع وألغِ حسب الـ slug:

$pay = $orcarail->pay->retrieve('pl_abc123'); // get() is an alias
$orcarail->pay->cancel('pl_abc123');

الاشتراكات​

تستنتج مسارات مجموعة الاشتراكات المؤسسة من مفتاح API المصادَق عليه. أنشئ إما بـ price_id أو بالمجموعة الكاملة من المعاملات المباشرة (amount وcurrency وtoken_id وnetwork_id وinterval)؛ لاحظ أن أسماء الحقول هنا بصيغة snake_case بما يطابق عقد REST.

$subscription = $orcarail->subscriptions->create([
'description' => 'Monthly Pro Plan',
'amount' => '10.00',
'currency' => 'usd',
'token_id' => $tokenId,
'network_id' => $networkId,
'interval' => 'month',
'interval_count' => 1,
'collection_method' => 'send_payment_link',
'total_cycles' => 12,
'payer_email' => '[email protected]',
'metadata' => ['plan_id' => 'pro_monthly'],
]);

echo $subscription->id;
echo $subscription->current_period_end;
echo $subscription->latest_payment_link?->link;

السرد والتحديث والإلغاء والاستئناف:

$page = $orcarail->subscriptions->all([
'status' => 'active',
'limit' => 20,
'current_period_end' => ['lte' => '2026-12-31T00:00:00.000Z'], // sent as current_period_end[lte]
]);

foreach ($page->data as $sub) {
echo $sub->id . ' ' . $sub->description . PHP_EOL;
}
echo $page->has_more ? 'more pages' : 'last page';

$orcarail->subscriptions->update($subscription->id, ['cancel_at_period_end' => true]);

$orcarail->subscriptions->cancel($subscription->id, [
'cancellation_details' => ['comment' => 'Switching to annual', 'feedback' => 'other'],
]);

$orcarail->subscriptions->resume($subscription->id);

$links = $orcarail->subscriptions->listPaymentLinks($subscription->id, ['limit' => 10]);

الدالة هي all() لا list() لأن list كلمة محجوزة في PHP.

استدعاءات الخصم التلقائي الموجهة للدافع (approve-auto-charge وrevoke-auto-charge وset-collection-method) ليست جزءًا من الـ SDK؛ استدعِها عبر HTTP دون مفتاح API من واجهة الدفع لديك. راجع الخصم التلقائي.

الكتالوج: المنتجات والأسعار​

مسارات الكتالوج مرتبطة بالمؤسسة، لذا مرّر معرّف UUID للمؤسسة كأول وسيط.

$products = $orcarail->products->all($organizationId);
$product = $orcarail->products->create($organizationId, [
'name' => 'Pro Plan',
'description' => 'Monthly access',
'active' => true,
]);

$prices = $orcarail->prices->all($organizationId, ['active' => true]);
$recurring = $orcarail->prices->listActiveRecurring($organizationId);

// Find or create a one-time price for a fixed amount (idempotent by amount + currency + product name)
$price = $orcarail->prices->ensureOneTime($organizationId, [
'amount' => '25.00',
'currencyCode' => 'usd',
'tokenId' => $tokenId,
'networkId' => $networkId,
'productName' => 'One-time checkout',
]);

$intent = $orcarail->paymentIntents->create([
'price_id' => $price->id,
'return_url' => 'https://yourapp.com/success',
]);

تفك products->all() وprices->all() غلاف القائمة في API وتعيدان مصفوفات من OrcaRailObject.

الأسعار​

$quote = $orcarail->rates->getFiatQuote(['amount' => '100.00', 'currency' => 'eur']);
echo $quote->amountUsd;

$currencies = $orcarail->rates->getCurrencies(['active' => true]);
foreach ($currencies as $currency) {
echo $currency->code . PHP_EOL;
}

راجع API الأسعار لحقول الاستجابة.

الـ webhooks​

الأدوات المساعدة للـ webhooks ثابتة (static) ولا تحتاج إلى عميل، لأن التحقق لا يتطلب سوى الجسم الخام وترويسة x-webhook-signature وسر الـ webhook الخاص بك.

التحقق من توقيع​

use OrcaRail\WebhookSignature;

$payload = file_get_contents('php://input'); // raw body, never a re-encoded array
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';

if (!WebhookSignature::verifyHeader($payload, $signature, $webhookSecret)) {
http_response_code(400);
echo 'Invalid signature';
exit;
}

بناء حدث (التحقق والتحليل)​

use OrcaRail\Enum\WebhookEventType;
use OrcaRail\Exception\SignatureVerificationException;
use OrcaRail\Webhook;

try {
$event = Webhook::constructEvent(
file_get_contents('php://input'),
$_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '',
getenv('ORCARAIL_WEBHOOK_SECRET'),
);
} catch (SignatureVerificationException $exception) {
http_response_code(400);
exit;
}

$object = $event->data->object;
$type = WebhookEventType::tryFrom((string) $event->type);

match ($type) {
WebhookEventType::PaymentIntentCompleted => fulfilOrder($object->id),
WebhookEventType::PaymentIntentProcessing => markProcessing($object->id),
WebhookEventType::PaymentIntentCanceled => releaseOrder($object->id),
WebhookEventType::SubscriptionPaymentLinkPaid => creditCycle($object->id),
// tryFrom() returns null for event types this SDK version does not know
default => null,
};

http_response_code(200);
echo json_encode(['received' => true]);

ترمي constructEvent() الاستثناء SignatureVerificationException عند توقيع غير صالح، أو JSON مشوّه، أو جسم ليس كائن JSON. أجب دائمًا بـ 200 فقط بعد قبول الحدث.

مثال Laravel​

اقرأ جسم الطلب الخام؛ فالمصفوفة المحلَّلة لن تجتاز التحقق.

use Illuminate\Http\Request;
use OrcaRail\Exception\SignatureVerificationException;
use OrcaRail\Webhook;

Route::post('/webhooks/orcarail', function (Request $request) {
try {
$event = Webhook::constructEvent(
$request->getContent(),
$request->header('x-webhook-signature', ''),
config('services.orcarail.webhook_secret'),
);
} catch (SignatureVerificationException) {
return response('Invalid signature', 400);
}

// Dispatch a job keyed by $event->data->object->id, then acknowledge.
return response()->json(['received' => true]);
});

استثنِ المسار من التحقق من CSRF، وتأكد من أن أي middleware لا يعيد كتابة الجسم قبل التحقق.

مثال Symfony​

use OrcaRail\Exception\SignatureVerificationException;
use OrcaRail\Webhook;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class OrcaRailWebhookController
{
#[Route('/webhooks/orcarail', methods: ['POST'])]
public function __invoke(Request $request): Response
{
try {
$event = Webhook::constructEvent(
$request->getContent(),
$request->headers->get('x-webhook-signature', ''),
$_ENV['ORCARAIL_WEBHOOK_SECRET'],
);
} catch (SignatureVerificationException) {
return new Response('Invalid signature', Response::HTTP_BAD_REQUEST);
}

return new JsonResponse(['received' => true]);
}
}

معالجة الأخطاء​

ترمي الـ SDK استثناءات مصنّفة الأنواع ترث كلها من OrcaRail\Exception\OrcaRailException (وهو نفسه RuntimeException).

use OrcaRail\Exception\ApiException;
use OrcaRail\Exception\AuthenticationException;
use OrcaRail\Exception\OrcaRailException;
use OrcaRail\Exception\TransportException;

try {
$intent = $orcarail->paymentIntents->create([
'price_id' => $priceId,
'return_url' => 'https://yourapp.com/success',
]);
} catch (AuthenticationException $exception) {
// 401 — invalid API key or secret
error_log($exception->getMessage());
} catch (ApiException $exception) {
// Any other non-2xx response
error_log($exception->statusCode . ' ' . $exception->type . ' ' . $exception->getMessage());
error_log(json_encode($exception->request)); // method and URL of the failed call
} catch (TransportException $exception) {
// Network failure, timeout, or unparseable response
error_log($exception->getMessage());
} catch (OrcaRailException $exception) {
error_log($exception->getMessage());
}

تؤدي تركيبات المعاملات غير الصالحة (مثل price_id مع amount) إلى رمي InvalidArgumentException قبل إجراء أي طلب HTTP.

الاختبار دون شبكة​

مرّر وسيلة نقل خاصة بك كـ http_client لتسجيل الطلبات في الاختبارات. يعمل أي صنف ينفّذ OrcaRail\HttpClient\ClientInterface، ولا يلزم مفتاح API.

use OrcaRail\HttpClient\ClientInterface;
use OrcaRail\OrcaRailClient;

$recorder = new class implements ClientInterface {
/** @var list<array{string, string, ?array<string, mixed>, bool}> */
public array $calls = [];

public function request(string $method, string $path, ?array $body = null, bool $requireAuth = true): mixed
{
$this->calls[] = [$method, $path, $body, $requireAuth];

return ['id' => 'pi_test', 'status' => 'requires_confirmation'];
}
};

$orcarail = new OrcaRailClient(['http_client' => $recorder]);
$intent = $orcarail->paymentIntents->create([
'price_id' => 'price_test',
'return_url' => 'https://example.com/return',
]);

// $recorder->calls[0] === ['POST', 'payment_intents', [...], true]

انظر أيضًا​