المطورون حالات الاستخدام

ربط الفوترة الإلكترونية مع Laravel

أصدر الفواتير الضريبية السعودية من تطبيق Laravel بعميل HTTP المدمج: الإعدادات، وصنف خدمة، ومهمة في الطابور، ومعالجة الأخطاء.

لا تحتاج أي حزمة: عميل 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 وأرسل أول طلب خلال دقائق.