# Order ratings (تقييم الطلبات) — mobile app contract

**One rating per purchase.** A checkout from several sellers is split into several orders (one per
seller, same `combined_order_id`), but the customer rates it once. The backend asks when **every**
order of the purchase is delivered (cancelled ones aside). Any `order_id` of the purchase can be used
in the endpoints below; they all resolve to the same rating.

The app shows a rating screen:

- **5 stars**: stars + optional comment is enough.
- **1–4 stars**: open the criteria from the `criteria` list (each 1–5 stars, all required) + optional comment.
  There are six today; the list comes from the server, so build the rows from it, never from a fixed list.

If the whole purchase holds a single product, the backend also publishes the rating as that product's review.
Ratings below 5 are emailed to the team. The app does not need to handle either.

All endpoints need the usual headers (`System-Key`, `Authorization: Bearer <sanctum token>`).
Base: `/api/v2`.

## 1. Push notification (FCM)

Sent once, right after the last order of the purchase is marked delivered:

```json
{
  "notification": { "title": "كيف كان طلبك؟", "body": "قيّم طلبك 20260928-00082141 وساعدنا نخدمك بشكل أفضل" },
  "data": {
    "item_type": "order_rating",
    "item_type_id": "5123",
    "link": "",
    "click_action": "FLUTTER_NOTIFICATION_CLICK"
  }
}
```

On tap: open the rating screen for order `item_type_id` (the purchase's first order) (load it with `GET order-ratings/{order_id}`).

An inbox entry also appears in `GET all-notification`. It has `type: "App\\Notifications\\OrderNotification"`
and `data.status: "rate"`. Tapping it should open the same rating screen (`data.order_id`).

**Delivered by mistake.** If staff move the order back from delivered before the customer rates it, the
request is taken back: the inbox entry disappears from `GET all-notification`, `pending_order_rating_id`
is `null`, and the real delivery later sends a new push. A push that already reached the phone cannot be
recalled, so on tap (push or inbox) always load `GET order-ratings/{order_id}` and show the form only when
`can_rate` is `true`; otherwise show the order (or a short "you can rate it once it is delivered").

## 2. On app open: is there an order to rate?

Either field works:

- `GET profile/counters` has a new key, `"pending_order_rating_id": 5123` (or `null`).
- `GET order-ratings/pending` returns the full screen data:

```json
{
  "result": true,
  "data": {
    "order_id": 5123,
    "order_code": "20260928-00082141",
    "order_ids": [5123, 5124],
    "order_codes": ["20260928-00082141", "20260928-00082142"],
    "combined_order_id": 2210,
    "date": "28-09-2026",
    "single_product": true,
    "items": [
      { "product_id": 812, "name": "ماسك وجه SADOER", "thumbnail": "https://…/uploads/….webp", "quantity": 2 }
    ],
    "criteria": [
      { "key": "shipping",  "label": "الشحن والتوصيل" },
      { "key": "packaging", "label": "التغليف والحماية" },
      { "key": "accuracy",  "label": "مطابقة الوصف والصور" },
      { "key": "price",     "label": "سعر المنتج" },
      { "key": "service",   "label": "خدمة العملاء والتواصل" },
      { "key": "app",       "label": "تجربة الشراء من التطبيق" }
    ],
    "status": "pending",
    "can_rate": true,
    "rating": null
  }
}
```

`items` covers every seller's order of the purchase. `order_id`/`order_code` are its first order;
show `order_codes` when there are several.

`data` is `null` when there is nothing to ask. The backend returns the newest delivered, unrated purchase
that was not dismissed, within 30 days of delivery. Show the popup when `data` is not null.

Use the `criteria` list (keys + translated labels) to build the rows; do not hard-code them. New questions
may be added later (the server only needs a config line), and the app then shows them with no update.

## 3. One order's rating screen

`GET order-ratings/{order_id}` returns the same shape as above. When the order is already rated:
`"status": "rated"`, `"can_rate": false` and

```json
"rating": {
  "rating": 3, "comment": "العلبة وصلت مفتوحة", "rated_at": "29-09-2026 21:23",
  "scores": { "shipping": 2, "packaging": 1, "accuracy": 4, "price": 4, "service": 3, "app": 5 }
}
```

`404 {"result": false}` means the order is not this customer's.

`GET purchase-history-details/{id}` now also returns `can_rate` (bool) and `order_rating` (the object
above or `null`). Both describe the purchase, so every seller's order of it shows the same rating.
`can_rate` stays `false` until the whole purchase is delivered. Use them for a "Rate order" button
on the order page.

`GET purchase-history` (the orders list) returns two keys on every order:

| key | value |
|---|---|
| `rating_state` | `"can_rate"`: the whole purchase is delivered and not rated yet: show "قيّم الطلب". `"rated"`: show the stars. `null`: show nothing (not delivered yet, cancelled or returned). |
| `rating_stars` | 1–5 when `rating_state` is `"rated"`, else `null` |

Both describe the purchase, so a purchase from two sellers shows the same state on both rows (one
rating for both). On tap, open `GET order-ratings/{id}` as above: it shows the form or the rating given.
After a submit, reload the list (or set the row to `"rated"` with the stars just sent).

## 4. Submit

`POST order-ratings/{order_id}` (form or JSON body), e.g. `{"rating": 3, "scores": {"shipping": 2, "packaging": 1, "accuracy": 4, "price": 4, "service": 3, "app": 5}, "comment": "…"}`

| field | rule |
|---|---|
| `rating` | required, integer 1–5 |
| `scores` | object `{criterion key: 1–5}` with **every** key of `criteria`; required when `rating` < 5, ignored when `rating` = 5. Form-encoded: `scores[shipping]=2&scores[packaging]=1…` |
| `comment` | optional, max 2000 characters (emoji allowed) |

Success, `200`:

```json
{ "result": true, "message": "شكراً لك! تم استلام تقييمك.", "data": { "rating": 3, "comment": "…", "rated_at": "29-09-2026 21:23", "scores": { "shipping": 2, "…": 5 } } }
```

Errors, `422 {"result": false, "message": "…"}`: a missing or invalid field, a purchase that is not
fully delivered, or one that is already rated (from any of its orders). Show `message` as is; it is already translated.

## 5. "Don't ask me again"

`POST order-ratings/{order_id}/dismiss` returns `{ "result": true }`. That purchase never pops up again,
but it can still be rated from its order page.

"Later" (closing the popup) needs no call. The popup comes back next time the app opens.
