---
feature: Provider selection and price
slug: provider-selection
surface: client
consumer: flutter
version: 2.1.0
status: ready
brd_version: "1.3"
brd_ids: [CUST-010, CUST-011]
business_rules: [BR-02, BR-05, BR-06, BR-08, BR-09, BR-11]
decisions: [DEC-04, DEC-09, SCR-13, TD-19, TD-42, TD-78, TD-92, TD-93]
schema_tables: [providers, provider_services, provider_working_hours, provider_time_off, orders, commission_rules]
screens: [customer--booking--01-*, customer--booking--02-*]
related_docs: [booking.md, orders.md, ../provider/availability.md]
last_updated: 2026-10-25
---

# Provider selection and price — Flutter Integration Guide (client)

> **Base URL:** `{HOST}/api/v1/client` · [Shared API conventions](../API_CONVENTIONS.md).

## Version history

| Version | Date | Type | Summary | App action required |
|---------|------|------|---------|---------------------|
| 2.1.0 | 2026-11-09 | MINOR | `GET /providers/{provider}/slots` and the quote accept `people` for a group booking (D2, TD-180); slots then fit the combined duration and return `estimated_duration_min`. | Use [booking](booking.md) §3.5 for groups. |
| 2.0.0 | 2026-10-25 | **Breaking** | The quote's `booking_type` is `{value,label}`; instant orders now block arrival time in slots. | Read `booking_type.value` from the quote. |
| 1.0.0 | 2026-10-25 | Initial | Eligible providers, scheduled slots, exact quote. | Pass address, booking type and selected choice UUIDs. |

## Changelog

### 2.1.0 — 2026-11-09

- **Added:** optional `people` on slots and quote; `data.service_id` is `null` and `data.estimated_duration_min` is present on a group slots response.
- **Breaking:** No. **App action required:** None for single-service flows.

### 2.0.0 — 2026-10-25
- **Changed (breaking):** `data.booking_type` of `POST /orders/quote` is `{"value","label"}` (was a string).
- **Fixed:** slots and instant availability treat an instant order as busy from the moment it is placed until arrival (ETA) plus the service, so a scheduled slot can no longer overlap an instant visit (TD-92). Instant eligibility needs ETA + service duration to fit inside her working hours.
- **App action required:** read `booking_type.value`; reload slots after a conflict.
- **BRD / decisions:** CUST-010/011, TD-92.

### 1.0.0 — 2026-10-25
- **Added:** Provider cards, slots, quote. **Changed/Fixed/Removed:** None. **Breaking:** No.
- **Endpoints affected:** `GET /services/{service}/providers`, `GET /providers/{provider}/slots`, `POST /orders/quote`.
- **App action required:** Show provider prices from this API, request slots for scheduled booking, and refresh quote before submit.
- **BRD / decisions:** CUST-010/011, BR-05/06/08/09/11, TD-42/78/92/93.

## 1. Business summary

A customer chooses the active service tree's required options and her saved address. The server filters approved, accepting, covered, fully priced providers using `EligibleProviders`, then ranks eligible Featured providers. A price is the chosen provider's service price plus option prices and a delivery fee; commission configuration must permit checkout (TD-93).

## 2. User flow and screens

| Step | Screen / widget | Endpoint | App behavior |
|------|-----------------|----------|--------------|
| 1 | Booking 01/02 provider cards | `GET /services/{service}/providers` | Show price including delivery, rating, distance and empty state. Never invent a price from catalog minimums. |
| 2 | Scheduled date/time | `GET /providers/{provider}/slots` | Render returned Saudi-local ISO starts only; refresh if address/options change. |
| 3 | Summary | `POST /orders/quote` | Render all amount strings and the ETA/scheduled time; proceed to [booking](booking.md). |

## 3. Endpoints

### 3.1 Eligible provider cards

`GET /api/v1/client/services/{service}/providers` · customer bearer token · `geo.context`.

| Field | Place | Required | Rule |
|-------|-------|----------|------|
| `service` | path | yes | Type-1 service UUID. |
| `address_id` | query | yes | Owned address UUID. |
| `booking_type` | query | yes | `instant` or `scheduled`. |
| `option_ids[]` | query | no | Active choice UUIDs; required groups must be satisfied. |

Response `data[]` includes `id`, `name`, `photo`, `rating_avg`, `rating_count`, `service_price`, `delivery_fee`, `total_amount`, `distance_km`, `coverage`, `can_receive_instant`. The array may be empty. **Real response:** Postman: `client.booking.providers` › "200 Eligible provider with selected prices and delivery". Validation: "422 Missing required booking fields"; missing choice: `INVALID_SERVICE_OPTIONS` (422); commission configuration: `COMMISSION_RULE_UNAVAILABLE` (422, generic message).

### 3.2 Scheduled slots

`GET /api/v1/client/providers/{provider}/slots` · customer bearer token.

| Field | Place | Required | Rule |
|-------|-------|----------|------|
| `provider` | path | yes | Provider UUID. |
| `service_id` | query | yes | Type-1 service UUID. |
| `date` | query | yes | Saudi-local `YYYY-MM-DD`. |
| `address_id` | query | yes | Owned address UUID. |
| `option_ids[]` | query | no | Choice UUIDs used for full duration. |

`data.slots` is a list of ISO 8601 times with `+03:00`. It excludes closed hours, time off, overlap and starts before the configured minimum lead. **Real responses:** Postman: `client.booking.slots` › "200 Scheduled slots excluding time off"; "422 Missing required booking fields".

### 3.3 Exact quote

`POST /api/v1/client/orders/quote` · customer bearer token · JSON.

| Field | Required | Rule |
|-------|----------|------|
| `service_id`, `provider_id`, `address_id` | yes | Public UUIDs; address must be owned. |
| `booking_type` | yes | `instant` or `scheduled`. |
| `scheduled_at` | scheduled | ISO 8601 with offset; bookable slot. |
| `option_ids` | yes | Array, empty only when all required groups are satisfied. |

`data` has provider/address, `booking_type` `{value,label}`, selected service/options, `services_amount`, `delivery_fee`, `discount_amount`, `vat_amount`, `total_amount` (money strings), `distance_km`, `eta_minutes`, `scheduled_at`. It writes no order. **Real response:** Postman: `client.orders.quote` › "200 Instant order quote"; errors include "422 Required choice missing", "422 No provider commission rule", "422 Fixed commission cannot be snapshotted", and "422 Missing required booking fields". The quote and create route share `CalculateOrderQuote`; create rechecks current state inside a transaction.

## 4. Statuses and enums

| Enum | Values | Meaning |
|------|--------|---------|
| booking_type | `instant`, `scheduled` | Instant needs acknowledged opt-in and current open hours; scheduled needs a slot. |

## Data model

| Table | Read / write | Notes |
|-------|--------------|-------|
| `provider_services`, `providers`, `provider_working_hours`, `provider_time_off`, `orders` | read | Eligibility, own prices, full-duration availability. |
| `commission_rules` | read | Latest active effective provider/service percent rule required (TD-93). |

## 5. Dart models

Parse provider/order IDs as `String` UUIDs, all money as exact decimal strings, `distance_km` as a decimal string, and slot times with their offset. Treat `eta_minutes` and `scheduled_at` as nullable. Keep `option_ids` as a list of UUID strings.

## 6. Business rules enforced by the backend

| Rule | Server behavior | App response |
|------|-----------------|--------------|
| BR-05/06 | Validates required active groups and each provider's option price. | Keep the selection screen open on `INVALID_SERVICE_OPTIONS`. |
| BR-08/09 | Filters eligibility, then ranks Featured. | Show an empty state if none qualify. |
| BR-11 | Computes distance and fee from the selected address/provider coordinates. | Ask for a mapped address on `DISTANCE_UNAVAILABLE`. |
| TD-92 | Full estimated duration blocks overlapping starts; 15-minute slots and 60-minute lead are working values in settings. | Reload slots after a conflict. |
| TD-93 | Missing/fixed commission rule blocks quote/create with generic text. | Show retry/support text; customer cannot repair configuration. |

## 7. Notifications and real-time events

N/A for selection. Poll order details after submission; the request/decision events are in [orders](orders.md).

## 8. Localization and UI notes

Names and labels are localized; Arabic is fallback. Money is SAR, exact two-decimal text. `scheduled_at` is an absolute ISO instant; show it in the customer's Saudi-local timezone.

## 9. Integration checklist

- [ ] Send UUIDs, complete option groups and the owned address.
- [ ] Render server price, fee and total; refresh quote at final review.
- [ ] Render only returned slots, with empty/loading/error states.
- [ ] Handle `PROVIDER_UNAVAILABLE`, `BOOKING_TIME_UNAVAILABLE`, `INVALID_SERVICE_OPTIONS`, `DISTANCE_UNAVAILABLE`, `COMMISSION_RULE_UNAVAILABLE`.
- [ ] Verify Arabic RTL and English layouts against recorded Postman responses.

## 10. Acceptance scenarios

| AC ID | Scenario | Expected |
|-------|----------|----------|
| AC-CUST-010 | Pick a provider | Only eligible providers; Featured affects ordering after filtering. |
| AC-CUST-011 | Price selected choices and delivery | Chosen provider's prices; `130.00 + 20.00 = 150.00` in the recorded fixture. |
| AC-CUST-013 | Inspect slots | Closed, time-off and overlapping starts absent. |

## 11. Open decisions and known limitations

DEC-04 capacity and slot holds remain open; TD-92 supplies working overlap and time values, **values proposed by the project owner on 2026-10-25; DEC-05 stays Partly resolved until the client confirms the numbers**. Fixed-amount provider commission is not supported at checkout until the earnings feature adds a snapshot column (TD-93). The Phase 2 simulated payment driver is a launch blocker (TD-94).

Out of scope: group booking (D2), packages and package sessions (D1), coupons, VAT on orders, wallet/payment/invoice screens, cancellation, transport choice and driver trips, execution beyond `confirmed`, chat, provider home stats/earnings, rating, and realtime sockets.
