# واجهة دفع الفواتير — مواصفات واجهة برمجة التطبيق (الشركة)

## سياق الصورة الكلية

تستهلك تطبيقات الشركة بالفعل النهايتين:
- `GET /api/v1/company/invoices` — قائمة الفواتير
- `GET /api/v1/company/invoices/{invoice}` — تفاصيل فاتورة واحدة

الشاشة المخصصة للدفع تدعم طريقتين:
1. الدفع الإلكتروني ببطاقات الصراف الآلي.
2. التحويل البنكي متبوعًا برفع إيصال التحويل.

لا تشمل وثائق واجهة الشركة الحالية ولا مجموعة Postman النهايات اللازمة لإتمام أيّة من المسارين. البيئة التالية تُوثّق العقود الحالية للنهايات وإجاباتها الفعلية.

---

## 08. فواتير — الدفع والإيصالات

> الفواتير تُولَّد آليًا شهريًا ولا تُنشأ عبر الواجهة (لا توجد نهاية `store` للفواتير في أيّ من الدور) — انظر `docs/api/WORKFLOWS.md` §9.

---

### الحصول على طرق الدفع المتاحة وتفاصيل الحساب البنكي

**ما تفعله:** تُعيّن الطرق المتاحة للدفع حسب حالة الفاتورة الحالية (سيناريو البوابة الإلكترونية، أو التحويل البنكي، أو كليهما)، وتُعيد بيانات الحساب البنكي المُعلمَة في `app_configs`.

| | |
|---|---|
| **الطريقة** | `GET` |
| **النهاية** | `{{base_url}}/api/v1/company/invoices/{invoice}/payment-options` |
| **اسم المسار** | `company.invoices.payment-options` |
| **الحارس** | Bearer JWT (`auth:api`)، الحساب من `user_type: company` ويجب أن يكون `active` |
| **رموز الحالة** | `200 OK`، `401 Unauthorized`، `404 Not Found` |

**الرؤوس:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`
- `Authorization: Bearer {token}`

لا توجد معلمات مسار أو استعلام إلّا `{invoice}` في المسار.

**الاستجابة الناجحة الفعلية:**

`200 OK — خيارات الدفع RSS` (فاتورة `issued`، الطريقتان متاحتان)
```json
{
    "status": "success",
    "message": "Invoice payment options retrieved successfully.",
    "data": {
        "available_methods": [
            "gateway",
            "bank_transfer"
        ],
        "bank_transfer": {
            "bank_name": "Al Rajhi Bank",
            "beneficiary_name": "Integrated Maintenance Company",
            "iban": "SA0000000000000000000000"
        }
    }
}
```

`200 OK — خيارات الدفع PSD2` (فاتورة `pending`، لا طرق دفع)
```json
{
    "status": "success",
    "message": "Invoice payment options retrieved successfully.",
    "data": {
        "available_methods": [],
        "bank_transfer": {
            "bank_name": null,
            "beneficiary_name": null,
            "iban": null
        }
    }
}
```

**نقاط تصميم (مستند عليها من الكود highway، لا تُخمّن):**
- `available_methods` لا يُقرّر على مستوى الفاتورة فحسب، بل يندرج من `Invoice::canPayOnline()` و `canUploadReceipt()` / `canReplaceReceipt()` — أي أن الطرق تتغير تلقائيًا حسب حالة الفاتورة.
- بيانات الحساب البنكي تُخزن في جدول `app_configs` تحت المفاتيح: `bank_transfer_bank_name`، `bank_transfer_beneficiary_name`، `bank_transfer_iban` — وبالتالي يمكن تغييرها دون إصدار إصدار جديد للتطبيق.
- الإيبان يُعاد كما هو مُخزَّن في قاعدة البيانات (غير منسّق)؛ إذا أُريد تنسيقه للمجموعات فعلي التطبيق إجراء التنسيق화면屏蔽.

---

### بدء جلسة دفع إلكتروني

**ما تفعله:** يبدأ جلسة دفع عبر البوابة المُعيَّنة في `X-Platform`، ويُنشئ سجلّ `Payment` على حالته `pending`، ثم يُعيد رابط تحويل العملاء إلى بوابة الصراف الآلي ورمز المرجع.

| | |
|---|---|
| **الطريقة** | `POST` |
| **النهاية** | `{{base_url}}/api/v1/company/invoices/{invoice}/payments/gateway` |
| **اسم المسار** | `company.invoices.payments.gateway` |
| **الحارس** | Bearer JWT (`auth:api`)، الحساب `company` و`active`،middleware `idempotency` مفعل |
| **رموز الحالة** | `201 Created`، `401 Unauthorized`، `404 Not Found`، `409 Conflict`، `422 Unprocessable Entity` |

**الرؤوس:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`
- `Authorization: Bearer {token}`
- `Idempotency-Key: {uuid}` — إلزامي (أمان إعادة المحاولة)

**المحتوى المطلوب:**

| حقل | نوع | إلزامي | ملاحظات |
|---|---|---|---:|
| `callback_url` | سلسلة | اختياري | محدود بـ 500 حرف، يُستخدم لاسترداد حالة الدفع بعد الانتهاء |

**مثال جسم الطلب:**
```json
{
    "callback_url": "seiana-company://invoice-payment/callback"
}
```

**الاستجابة الناجحة الفعلية:**

`201 Created — إنشاء جلسة دفع` (بوابة متاحة، الفاتورة `issued`)
```json
{
    "status": "success",
    "message": "Payment session created successfully.",
    "data": {
        "payment_id": 7,
        "payment_url": "https://gateway.example.com/checkout/abc123",
        "expires_at": "2026-09-07T12:30:00+03:00"
    }
}
```

**أخطاء إلزامية مع أمثلة فعلية:**

`409 Conflict — جلسة معلقة بالفعل` (الفاتورة `issued`، وآخر دفع gateway على حالة `pending`)
```json
{
    "status": "fail",
    "message": "A payment for this invoice is already being processed.",
    "data": null
}
```

`422 Unprocessable Entity — لا يمكن دفع الفاتورة في حالتها الحالية` (الفاتورة `paid` أو `cancelled` أو `awaiting_verification`)
```json
{
    "status": "fail",
    "message": "This invoice cannot be paid in its current state.",
    "data": null
}
```

`422 Unprocessable Entity — فشل إنشاء جلسة الدفع` (عندما يُعيد `gateway->charge()` `success=false`)
```json
{
    "status": "fail",
    "message": "Unable to start a payment session. Please try again.",
    "data": null
}
```

**نقاط تصميم:**
- الفاتورة لا تقبل الدفع عبر البوابة إلا إذا كانت على أحد حالات: `issued`، `overdue`، `partially_paid` — انظر `Invoice::canPayOnline()`.
- لا تُنشأ أكثر من جلسة دفع `pending` واحدة لنفس الفاتورة في κάθε مرة؛ إذا أمكن.
- رابط الدفع له مهلة 30 دقيقة (يُحسب من لحظة الإنشاء `now()->addMinutes(30)`).
- `payment_id` هو المفتاح الأساسي لجدول `payments` (عدد صحيح)، بينما `uuid` يُخزن داخليًا ويساوي أيضًا `payment_id` بصيغة سلسلة في نظام المدفوعات المرفق بالفاتورة الخارجي — راجع `Payment::uuid`.

---

### التحقق من حالة الدفع الإلكتروني

**ما تفعله:** يُحقق من حالة دفعة البوابة الإلكترونية بالنظر إلى `gateway_reference` مع بوابة الصراف الآلي، ويحاول ترقية الحالة إلى `paid` في حال نجاح التحقق من البوابة، ثم يُطبّق المدفوعة على الفاتورة ويُعيد حالة الفاتورة والدفعة معًا.

| | |
|---|---|
| **الطريقة** | `GET` |
| **النهاية** | `{{base_url}}/api/v1/company/invoices/{invoice}/payments/{payment}/status` |
| **اسم المسار** | `company.invoices.payments.status` |
| **الحارس** | Bearer JWT (`auth:api`)، الحساب `company` و`active` |
| **رموز الحالة** | `200 OK`، `401 Unauthorized`، `404 Not Found`، `403 Forbidden` (الفاتورة تخص شركة أخرى) |

**الرؤوس:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`
- `Authorization: Bearer {token}`

لا توجد معلمات استعلام; كل ما هو مطلوب في المسار.

**الاستجابة الناجحة الفعلية:**

`200 OK — دفعة ناجحة` (الفاتورة `issued`، الدفعة `paid`، الفاتورة انتقلت إلى `paid`)
```json
{
    "status": "success",
    "message": "Payment status retrieved successfully.",
    "data": {
        "payment_id": 7,
        "payment_status": "paid",
        "invoice_status": "paid",
        "invoice_status_label": "Paid"
    }
}
```

`200 OK — دفعة لا تزال معلقة` (الفاتورة `issued`، الدفعة `pending`، لم يُكمل الصراف الآلي بعد)
```json
{
    "status": "success",
    "message": "Payment status retrieved successfully.",
    "data": {
        "payment_id": 7,
        "payment_status": "pending",
        "invoice_status": "issued",
        "invoice_status_label": "Due"
    }
}
```

**أخطاء إلزامية مع أمثلة فعلية:**

`404 Not Found — الدفعة غير موجودة` (الفاتورة موجودة لكن الدفعة المطلوبة غير مرتبطة بها)
```json
{
    "status": "fail",
    "message": "Resource not found",
    "data": null
}
```

`404 Not Found — الفاتورة غير موجودة أو تخص شركة أخرى` (المسار `company_id` يسترشد بالتوكن)
```json
{
    "status": "fail",
    "message": "Resource not found",
    "data": null
}
```

**حقول الاستجابة ومعانيها:**
- `payment_status`: حالة الدفعة الإلكترونية — أحد القيم في `lang/{locale}/api.php` → `payment_status`: `pending`، `paid`، `failed`، `cancelled`، `expired`.
- `invoice_status`: رمز حالة الفاتورة بعد تطبيق الدفع إن وجد (مثلاً `paid` أو `partially_paid` أو `issued`).
- `invoice_status_label`: ترجمة حالة الفاتورة من `api.invoice_status`.

**ملاحظات:** التطبيق مُحالْب أن ينتظر ظهور `payment_status: paid` قبل إغلاق شاشة الدفع وإعادة تحميل قائمة الفواتير أو تفاصيلها؛ لايعتمد على إغلاق المتصفح أو عودة التطبيق من WebView كدليل على النجاح.

---

### رفع إيصال التحويل البنكي

**ما تفعله:** يرفع ملف الإيصال (صورة أو PDF) كإيصال تحويل بنكي للفاتورة، ويُنشئ سجلّ `Payment` على `bank_transfer`، وسجلّ `BankTransferReceipt` على الحالة `pending`، ثم ينقّل الفاتورة إلى `awaiting_verification`.

| | |
|---|---|
| **الطريقة** | `POST` |
| **النهاية** | `{{base_url}}/api/v1/company/invoices/{invoice}/receipts` |
| **اسم المسار** | `company.invoices.receipts.store` |
| **الحارس** | Bearer JWT (`auth:api`)، الحساب `company` و`active` |
| **رموز الحالة** | `201 Created`، `401 Unauthorized`، `404 Not Found`، `422 Unprocessable Entity` |

**نوع المحتوى:** `multipart/form-data`

**حقول النموذج المطلوبة:**

| الحقل | النوع | إلزامي | ملاحظات |
|---|---|---:|:---|
| `receipt` | ملف | نعم | `mimes:jpg,jpeg,png,pdf`، أقصى حجم 8 ميغابايت |
| `payment_method` | سلسلة | نعم | القيمة المتوقعة: `bank_transfer` |
| `transfer_reference` | سلسلة | اختياري | محدود بـ 255 حرفًا، رقم المرجع أو العلم من البيان البنكي |
| `transferred_at` | تارخي زمني | اختياري | الصيغة: تاريخ صالح (`nullable|date`)، التوقيت المحلّي |

**مثال طلب (form-data):**
- `receipt`: `55.jpg` (ملف)
- `payment_method`: `bank_transfer`
- `transfer_reference`: `TR-2026-0907-001`
- `transferred_at`: `2026-09-07`

**الاستجابة الناجحة الفعلية:**

`201 Created — إيصال محمل` (الفاتورة `issued`، لا يوجد إيصال سابق)
```json
{
    "status": "success",
    "message": "Transfer receipt uploaded successfully.",
    "data": {
        "invoice_id": 3,
        "invoice_status": "awaiting_verification",
        "receipt": {
            "id": 55,
            "status": "pending_review",
            "status_label": "Under review",
            "notice": null,
            "file_path": "http://localhost/storage/receipts/55.jpg",
            "submitted_at": "2026-09-07T10:00:00+00:00"
        }
    }
}
```

ملاحظات على الاستجابة:
- `invoice_status` ينتقل من `issued` إلى `awaiting_verification` فور رفع الإيصال.
- `receipt.status` يكون `pending_review` (ليس `pending`) حسب ما هو مسجّل في `lang/{locale}/api.php` → `receipt_status`.
- `notice` يكون `null` حتى لا يُرفض الإيصال لاحقًا، حينها يُملأ بـ `rejection_reason` أو يُستHasher الرسالة الافتراضية `api.receipt_rejected_notice`.

**أخطاء إلزامية مع أمثلة فعلية:**

`422 Unprocessable Entity — يوجد Already إيصال` (الفاتورة لها إيصال `pending` أو `accepted` أو `rejected` بالفعل)
```json
{
    "status": "fail",
    "message": "A receipt already exists for this invoice.",
    "data": null
}
```

`422 Unprocessable Entity — لا يمكن دفع الفاتورة في حالتها الحالية` (الفاتورة `paid` أو `cancelled` أو Pending أخرى)
```json
{
    "status": "fail",
    "message": "This invoice cannot be paid in its current state.",
    "data": null
}
```

`422 Unprocessable Entity — مدخل إيصال غير صالح` (ملف بصيغة غير مدعومة أو حجم أكبر من 8 ميغابايت)
```json
{
    "status": "fail",
    "message": "The selected receipt is invalid.",
    "data": null
}
```

**ملاحظات تنفيذية:**
- الحد الأقصى للحجم: 8 ميغابايت (`max:8192` كيلوبايت).
- الصيغ المدعوْمة: `jpg`، `jpeg`، `png`، `pdf` (`mimes:jpg,jpeg,png,pdf`).
- لا يوجد دعم م isoform أخرى أو ملفات نصية خام.
- يُرسَل الملف毫安형태로 `multipart/form-data` هنا مباشرةً، ولا يُستHasher مسار رفع منفصل `POST /media` لرفع الإيصال (على عكس توقيع العقود).
- الإيصال الوحيد المسموح به لكل فاتورة في هذه النسخة: لا يوجد دعم لإيصالات متعددة في نفس الوقت، ويُست Hasher `latestReceipt()` لتحديد الإيصال الحالي.

---

### إعادة رفع إيصال مرفوض

**ما تفعله:** يُعيد رفع نسخة بديلة من إيصال تحويل بنكي للفاتورة التي لديها إيصال سابق على حالة `rejected`، فيكون الحال الجديد `pending_review` من جديد.

| | |
|---|---|
| **الطريقة** | `POST` |
| **النهاية** | `{{base_url}}/api/v1/company/invoices/{invoice}/receipts/{receipt}/resubmit` |
| **اسم المسار** | `company.invoices.receipts.resubmit` |
| **الحارس** | Bearer JWT (`auth:api`)، الحساب `company` و`active` |
| **رموز الحالة** | `201 Created`، `401 Unauthorized`، `404 Not Found`، `422 Unprocessable Entity` |

محتوى النموذج ومحدّدات الحجم والصيغ هي ذاتها تمامًا كما في `receipts.store` (انظر الجدول أعلاه).

**الاستجابة الناجحة الفعلية:**

`201 Created — إيصال معاد رفعه` (الفاتورة `awaiting_verification`، الإيصال السابق `rejected`)
```json
{
    "status": "success",
    "message": "Transfer receipt uploaded successfully.",
    "data": {
        "invoice_id": 3,
        "invoice_status": "awaiting_verification",
        "receipt": {
            "id": 56,
            "status": "pending_review",
            "status_label": "Under review",
            "notice": null,
            "file_path": "http://localhost/storage/receipts/56.jpg",
            "submitted_at": "2026-09-07T11:00:00+00:00"
        }
    }
}
```

`422 Unprocessable Entity — لا يمكن إعادة رفع الإيصال المرفوض فقط` (الإيصال المطلوب ليس `rejected` أو لا يوجد)
```json
{
    "status": "fail",
    "message": "Only a rejected receipt can be resubmitted.",
    "data": null
}
```

**ملاحظات تنفيذية:**
- `resubmitReceipt()` يتأكد أن `$receipt->id` يطابق المسار `receipt` الذي في الطلب، وأن `invoice->canReplaceReceipt()` يعود `true` — أي أن الإيصال الأخير `rejected`.
- الإيصالات التي لم تُرفض (مثلاً `accepted` أو `pending_review`) لا تُعاد، ويُعاد رفعها كإيصال جديد/cmd.

---

### إضافات استجابة تفاصيل الفاتورة

**ما تفعله:** يضيف استجابة `GET /company/invoices/{invoice}` والحقول الإضافية التالية إلى `InvoiceDetailsResource`:

```json
{
    "data": {
        "id": 3,
        "status": "issued",
        "status_label": "Due",
        "allowed_actions": {
            "pay_online": true,
            "upload_transfer_receipt": true,
            "replace_transfer_receipt": false
        },
        "receipt": {
            "id": null,
            "status": null,
            "status_label": null,
            "notice": null,
            "file_path": null
        }
    }
}
```

- `allowed_actions`: يرشّد التطبيق إلى الإجراءات المسموح بها دون تكرار القواعد التجارية في التطبيق.
- `receipt`: هيكّل الإيصال الحالي أو `null` مع بنية ثابتة (حتى لو لم يُرفع أي إيصال).

**حقول `allowed_actions`:**
- `pay_online`: `true` إذا أتيحت الدفع الإلكتروني الآن (`Invoice::canPayOnline()`).
- `upload_transfer_receipt`: `true` إذا أتيح رفع إيصال تحويل بنكي جديد (`Invoice::canUploadReceipt()`).
- `replace_transfer_receipt`: `true` إذا أتيح استبدال إيصال مرفوض (`Invoice::canReplaceReceipt()`).

**ملاحظات تنفيذية:** الحقول هذه موجودة بالفعل في `InvoiceDetailsResource` و`InvoiceResource`، وعلي التطبيق الاعتماد عليها عوضًا عن استنتاج الصلاحية من حالة الفاتورة يدويًا.

---

### رؤوس الطلبات المطلوبة لأمان التكرار

| نهاية | Require Idempotency-Key |
|---|---|
| `POST /company/invoices/{invoice}/payments/gateway` | نعم (middleware `idempotency` مفعل على المسار) |
| `POST /company/invoices/{invoice}/receipts` | لا (لا يوجد middleware idempotency هنا في الكود الحالية) |
| `POST /company/invoices/{invoice}/receipts/{receipt}/resubmit` | لا |

---

## الردود الخلفية المطلوبة بعد تأكيد الواجهة

يرجى الردّ مع:
- مسارات النهايات النهائية وطرق HTTP.
- حقول الطلب الدقيقة ومحدّدات الصحة (Laravel validation rules).
- أجسام الاستجابة الناجحة والأخطاء الدقيقة.
- كل قيم حالة الدفع وحالة الإيصال.
- سلوك البوابة الإلكترونية في العودة (callback/redirect).
- مصدر تفاصيل الحساب البنكي ومفاتيح الاستجابة.
- حدود ملفات الإيصال والصيغ المدعومة.
- وثيقة شركة API Markdown محدثة ومجموعة Postman محدثة بأمثلة حقيقية.
