أمثلة بلغات متعدّدة
لا نُلزمك بأي مكتبة — الواجهة REST + JSON عادية. هذه دوال جاهزة للنسخ،
كلّها تُرسل Idempotency-Key وتتعامل مع status بشكل صحيح.
لا تُدرج المفتاح في الكود مباشرة. اقرأه من متغيّر بيئة، ونادِ واجهتنا من الخادم فقط.
<?php
declare(strict_types=1);
final class Fawtara
{
private string $base = 'https://fawtara.codexal.co/api/v1';
public function __construct(private string $apiKey) {}
public function createInvoice(array $invoice, string $idempotencyKey): array
{
return $this->request('POST', '/invoices', $invoice, [
'Idempotency-Key: ' . $idempotencyKey,
]);
}
public function getInvoice(string $id): array
{
return $this->request('GET', '/invoices/' . rawurlencode($id));
}
public function retry(string $id): array
{
return $this->request('POST', '/invoices/' . rawurlencode($id) . '/retry');
}
private function request(string $method, string $path, ?array $body = null, array $headers = []): array
{
$ch = curl_init($this->base . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => array_merge([
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json',
'Accept: application/json',
], $headers),
CURLOPT_POSTFIELDS => $body === null
? null
: json_encode($body, JSON_UNESCAPED_UNICODE),
]);
$raw = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($raw === false) {
throw new RuntimeException('Network error: ' . $err);
}
$data = json_decode((string) $raw, true) ?? [];
if ($code >= 400) {
$e = $data['error'] ?? [];
throw new RuntimeException(
sprintf('[%d %s] %s', $code, $e['type'] ?? 'unknown', $e['message'] ?? $raw)
);
}
return $data;
}
}
// ----- الاستخدام -----
$fawtara = new Fawtara(getenv('FAWTARA_API_KEY'));
$res = $fawtara->createInvoice([
'invoice_number' => 'INV-1001',
'payment_method' => 'cash',
'customer' => ['name' => 'شركة المثال', 'identity_type' => 'tax', 'identity_number' => '12345678'],
'lines' => [
['name' => 'خدمة استشارية', 'quantity' => 1, 'unit_price' => 150.000],
],
], 'order-99813');
$inv = $res['data'];
if ($inv['status'] !== 'accepted') {
// لا تُعلن النجاح للمستخدم — سجّل الخطأ وأبلغ المسؤول
error_log('Invoice not accepted: ' . json_encode($inv['error'], JSON_UNESCAPED_UNICODE));
} else {
echo "QR: {$inv['qr_content']}\n";
}
<?php
// config/services.php
'fawtara' => [
'key' => env('FAWTARA_API_KEY'),
'base' => env('FAWTARA_BASE', 'https://fawtara.codexal.co/api/v1'),
],
// app/Services/FawtaraClient.php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use RuntimeException;
class FawtaraClient
{
protected function http()
{
return Http::withToken(config('services.fawtara.key'))
->baseUrl(config('services.fawtara.base'))
->acceptJson()
->timeout(60)
->retry(3, 1000, throw: false);
}
public function createInvoice(array $payload, string $idempotencyKey): array
{
$res = $this->http()
->withHeaders(['Idempotency-Key' => $idempotencyKey])
->post('/invoices', $payload);
if ($res->failed()) {
throw new RuntimeException(
$res->json('error.type') . ': ' . $res->json('error.message')
);
}
return $res->json('data');
}
}
// app/Jobs/SubmitInvoice.php — الإرسال داخل Job أفضل من داخل الطلب
public function handle(FawtaraClient $fawtara): void
{
$order = $this->order;
$invoice = $fawtara->createInvoice([
'invoice_number' => $order->number,
'issue_date' => $order->created_at->toDateString(),
'payment_method' => $order->is_credit ? 'receivable' : 'cash',
'customer' => [
'name' => $order->customer_name,
'identity_type' => 'tax',
'identity_number' => $order->customer_tin,
'phone' => $order->customer_phone,
],
'lines' => $order->items->map(fn ($i) => [
'name' => $i->name,
'quantity' => $i->qty,
'unit_price' => $i->price,
'discount' => $i->discount,
'tax_percent' => $i->tax_rate,
])->all(),
'metadata' => ['order_id' => $order->id],
], "order-{$order->id}");
$order->update([
'fawtara_id' => $invoice['id'],
'tax_uuid' => $invoice['uuid'],
'tax_status' => $invoice['status'],
'qr_content' => $invoice['qr_content'],
]);
}
// fawtara.js
const BASE = process.env.FAWTARA_BASE || 'https://fawtara.codexal.co/api/v1';
const KEY = process.env.FAWTARA_API_KEY;
class FawtaraError extends Error {
constructor(status, type, message, body) {
super(`[${status} ${type}] ${message}`);
this.status = status;
this.type = type;
this.body = body;
}
}
async function call(method, path, body, extraHeaders = {}) {
const res = await fetch(BASE + path, {
method,
headers: {
Authorization: `Bearer ${KEY}`,
'Content-Type': 'application/json',
Accept: 'application/json',
...extraHeaders,
},
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
const e = data.error || {};
throw new FawtaraError(res.status, e.type || 'unknown', e.message || '', data);
}
return data;
}
export const fawtara = {
createInvoice: (invoice, idempotencyKey) =>
call('POST', '/invoices', invoice, { 'Idempotency-Key': idempotencyKey }),
getInvoice: (id) => call('GET', `/invoices/${encodeURIComponent(id)}`),
retry: (id) => call('POST', `/invoices/${encodeURIComponent(id)}/retry`),
usage: () => call('GET', '/usage'),
};
// ----- الاستخدام -----
try {
const { data: inv } = await fawtara.createInvoice({
invoice_number: 'INV-1001',
payment_method: 'cash',
customer: { name: 'شركة المثال', identity_type: 'tax', identity_number: '12345678' },
lines: [{ name: 'خدمة استشارية', quantity: 1, unit_price: 150.0 }],
}, 'order-99813');
if (inv.status !== 'accepted') {
console.error('not accepted:', inv.error);
} else {
console.log('QR:', inv.qr_content);
}
} catch (err) {
if (err.type === 'rate_limited') {
await new Promise(r => setTimeout(r, (err.body.error.retry_after ?? 5) * 1000));
// أعد المحاولة بنفس مفتاح منع التكرار
}
throw err;
}
# fawtara.py
import os, json, time, uuid
import requests
BASE = os.getenv("FAWTARA_BASE", "https://fawtara.codexal.co/api/v1")
KEY = os.environ["FAWTARA_API_KEY"]
class FawtaraError(Exception):
def __init__(self, status, type_, message, body=None):
super().__init__(f"[{status} {type_}] {message}")
self.status, self.type, self.body = status, type_, body
class Fawtara:
def __init__(self, api_key: str = KEY, base: str = BASE):
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"Accept": "application/json",
})
self.base = base
def _call(self, method, path, body=None, headers=None, retries=3):
for attempt in range(retries):
res = self.session.request(
method, self.base + path,
data=json.dumps(body, ensure_ascii=False).encode() if body else None,
headers=headers or {}, timeout=60,
)
if res.status_code == 429 and attempt < retries - 1:
time.sleep(int(res.headers.get("Retry-After", 5)))
continue
data = res.json() if res.content else {}
if res.status_code >= 400:
err = data.get("error", {})
raise FawtaraError(res.status_code, err.get("type", "unknown"),
err.get("message", ""), data)
return data
raise FawtaraError(429, "rate_limited", "exhausted retries")
def create_invoice(self, invoice: dict, idempotency_key: str) -> dict:
return self._call("POST", "/invoices", invoice,
{"Idempotency-Key": idempotency_key})["data"]
def get_invoice(self, invoice_id: str) -> dict:
return self._call("GET", f"/invoices/{invoice_id}")["data"]
def retry(self, invoice_id: str) -> dict:
return self._call("POST", f"/invoices/{invoice_id}/retry")["data"]
# ----- الاستخدام -----
if __name__ == "__main__":
api = Fawtara()
inv = api.create_invoice({
"invoice_number": "INV-1001",
"payment_method": "cash",
"customer": {"name": "شركة المثال", "identity_type": "tax",
"identity_number": "12345678"},
"lines": [{"name": "خدمة استشارية", "quantity": 1, "unit_price": 150.0}],
}, idempotency_key=str(uuid.uuid4()))
if inv["status"] != "accepted":
print("رفض:", inv["error"])
else:
print("QR:", inv["qr_content"])
// FawtaraClient.cs — .NET 8
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
public sealed class FawtaraException(int status, string type, string message)
: Exception($"[{status} {type}] {message}")
{
public int Status { get; } = status;
public string Type { get; } = type;
}
public sealed class FawtaraClient
{
private readonly HttpClient _http;
public FawtaraClient(string apiKey, string baseUrl = "https://fawtara.codexal.co/api/v1/")
{
_http = new HttpClient { BaseAddress = new Uri(baseUrl), Timeout = TimeSpan.FromSeconds(60) };
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
_http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<JsonElement> CreateInvoiceAsync(object invoice, string idempotencyKey)
{
using var req = new HttpRequestMessage(HttpMethod.Post, "invoices")
{
Content = JsonContent.Create(invoice)
};
req.Headers.Add("Idempotency-Key", idempotencyKey);
using var res = await _http.SendAsync(req);
var json = await res.Content.ReadFromJsonAsync<JsonElement>();
if (!res.IsSuccessStatusCode)
{
var err = json.GetProperty("error");
throw new FawtaraException(
(int)res.StatusCode,
err.GetProperty("type").GetString() ?? "unknown",
err.GetProperty("message").GetString() ?? "");
}
return json.GetProperty("data");
}
}
// ----- الاستخدام -----
var client = new FawtaraClient(Environment.GetEnvironmentVariable("FAWTARA_API_KEY")!);
var data = await client.CreateInvoiceAsync(new
{
invoice_number = "INV-1001",
payment_method = "cash",
customer = new { name = "شركة المثال", identity_type = "tax", identity_number = "12345678" },
lines = new[] { new { name = "خدمة استشارية", quantity = 1, unit_price = 150.000 } }
}, idempotencyKey: $"order-99813");
Console.WriteLine(data.GetProperty("status").GetString());
// FawtaraClient.java — Java 17+
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
public class FawtaraClient {
private static final String BASE = "https://fawtara.codexal.co/api/v1";
private final HttpClient http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(15)).build();
private final String apiKey;
public FawtaraClient(String apiKey) { this.apiKey = apiKey; }
public String createInvoice(String jsonBody, String idempotencyKey) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(BASE + "/invoices"))
.timeout(Duration.ofSeconds(60))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.header("Idempotency-Key", idempotencyKey)
.POST(HttpRequest.BodyPublishers.ofString(jsonBody, java.nio.charset.StandardCharsets.UTF_8))
.build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
if (res.statusCode() >= 400) {
throw new RuntimeException("Fawtara error " + res.statusCode() + ": " + res.body());
}
return res.body();
}
public static void main(String[] args) throws Exception {
String body = """
{
"invoice_number": "INV-1001",
"payment_method": "cash",
"customer": { "name": "شركة المثال" },
"lines": [ { "name": "خدمة استشارية", "quantity": 1, "unit_price": 150.000 } ]
}
""";
var client = new FawtaraClient(System.getenv("FAWTARA_API_KEY"));
System.out.println(client.createInvoice(body, "order-99813"));
}
}
مواصفة OpenAPI
لتوليد عميل جاهز بأي لغة، استخدم المواصفة: https://fawtara.codexal.co/app/docs/openapi.json
# مثال بـ openapi-generator
openapi-generator-cli generate \
-i https://fawtara.codexal.co/app/docs/openapi.json \
-g typescript-fetch \
-o ./fawtara-client