لا تحتاج أي حزمة: عميل HTTP في Laravel يكفي. يفترض هذا الدليل Laravel 10 أو أحدث.
1. الإعدادات
// .env
WONDERFATOORA_API_URL=https://fatoora.tw.sa/api/v1
WONDERFATOORA_API_KEY=wbk_test_xxxxxxxxxxxxxxxxxxxxxxxx
// config/services.php
'wonderfatoora' => [
'url' => env('WONDERFATOORA_API_URL'),
'key' => env('WONDERFATOORA_API_KEY'),
],
2. صنف خدمة
<?php
namespace App\Billing;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
class WonderFatoora
{
private function http(): PendingRequest
{
return Http::baseUrl(config('services.wonderfatoora.url'))
->withToken(config('services.wonderfatoora.key'))
->acceptJson()
->timeout(60)
// Retry network errors and 5xx only; external_reference keeps
// a retried call from issuing a second invoice.
->retry(3, 2000, fn ($e) => ! $e instanceof \Illuminate\Http\Client\RequestException
|| $e->response->serverError(), throw: false);
}
/** @return array the invoice, as the API returns it */
public function issueForOrder(Order $order): array
{
$response = $this->http()->post('invoices', [
'external_reference' => 'order-'.$order->id,
'issue_date' => $order->paid_at->toDateString(),
'customer' => ['id' => $order->customer->wonderfatoora_id],
'items' => $order->lines->map(fn ($line) => [
'description' => $line->name,
'quantity' => $line->quantity,
'unit_price' => $line->unit_price,
])->all(),
]);
if (! $response->json('ok')) {
throw new \RuntimeException($response->json('error.code', 'http_'.$response->status()));
}
return $response->json('data.invoice');
}
}
إعادة المحاولة تقتصر على أخطاء الشبكة وردود 5xx؛ فالرد 4xx طلب يحتاج تصحيحًا لا تكرارًا. ولأن كل طلب يحمل external_reference فإن إعادة المحاولة لا تصدر فاتورة ثانية أبدًا.
3. الإصدار من مهمة في الطابور
<?php
// Issue from a queued job, so a slow or failed call never blocks checkout.
class IssueOrderInvoice implements ShouldQueue
{
use Queueable;
public int $tries = 5;
public array $backoff = [10, 60, 300, 900];
public function __construct(public Order $order) {}
public function handle(WonderFatoora $api): void
{
$invoice = $api->issueForOrder($this->order); // idempotent per order
$this->order->update([
'invoice_id' => $invoice['id'],
'invoice_url' => $invoice['zatca']['deliverable'] ? $invoice['public_url'] : null,
]);
}
}
أرسل المهمة بعد سداد الطلب، من مستمع حدث مثلًا: IssueOrderInvoice::dispatch($order)->afterCommit();
4. معالجة الأخطاء
| error.code | في تطبيقك |
|---|---|
validation_failed |
خلل في ربط الحقول: سجّل error.details ونبّه مطورًا. |
invoice_not_issuable |
مشكلة بيانات (العنوان أو الرقم الضريبي أو سبب الإعفاء): اعرض error.message على مسؤول، ولا تعد المحاولة. |
insufficient_scope، forbidden |
صلاحيات المفتاح أو المستخدم: أصلح المفتاح، ولا تعد المحاولة. |
rate_limited |
أعد المهمة إلى الطابور بعد ثواني Retry-After. |
token_expired |
انتهت صلاحية المفتاح: نبّه مسؤولًا وأرسل له error.details.renew_url. |
unauthorized |
جُدّد المفتاح أو أُلغي: نبّه مسؤولًا. |
5. الاختبار
زيّف الواجهة في اختباراتك بـ Http::fake() وتحقق من المحتوى بـ Http::assertSent(). ثم وجّه العميل إلى منشأة بيئة الاختبار بمفتاح اختبار (wbk_test_…): تُصدر فيها الفواتير فعلًا بترقيمها ونتائج محاكاة للهيئة، دون إنشاء فواتير ضريبية.
صفحات ذات صلة
جاهز للبدء؟
الوصول إلى الواجهة البرمجية ضمن باقات: الأعمال والمؤسسات. أنشئ مفتاحًا من الإعدادات ← مفاتيح API وأرسل أول طلب خلال دقائق.