Flows
Al-Waseet Delivery Flow
Al-Waseet delivery provider integration flow.
Al-Waseet Delivery Provider Flow
Al-Waseet is an external last-mile delivery provider integrated under Sendy's delivery-provider abstraction. Stores link their Al-Waseet merchant credentials, and the system automatically pushes orders to Al-Waseet when a Sendy order is created or assigned to the Al-Waseet delivery company.
1) Architecture Overview
Store JWT request
│
▼
StoreDeliveryProviderService resolves store's Al-Waseet credentials
│
▼
IAlWaseetClient (AlWaseetClient) typed HttpClient → Al-Waseet REST API
- Credential storage:
StoreDeliveryProviderLink—UsernameEncrypted= Al-Waseet username,PasswordEncrypted= Al-Waseet password.TokenEncrypted/TokenFetchedAt= session token (auto-refreshed every 23 hours). - Dispatch discriminator: any linked delivery company whose
Slugis not"boxy"or"hi-express"routes to Al-Waseet. The expected slug is"al-waseet". - Auth model: session-based —
POST /login(multipart form) returns a bearer-style token. The token is stored encrypted and automatically re-fetched when it is older than 23 hours. - Serialization: multipart/form-data for all Al-Waseet request bodies.
2) Linking an Al-Waseet Account
Stores link/unlink delivery providers via the shared delivery-provider endpoints (not Al-Waseet-specific):
POST api/v1/store/delivery-providers/links
DELETE api/v1/store/delivery-providers/links/{linkId}
GET api/v1/store/delivery-providers/links
Request body for linking:
{
"deliveryCompanyId": "<uuid of the Al-Waseet DeliveryCompany record>",
"username": "<al-waseet-username>",
"password": "<al-waseet-password>"
}
On link, StoreDeliveryProviderService.LinkProviderAsync immediately calls IAlWaseetClient.LoginAsync to validate the credentials. If login succeeds, the session token is encrypted and saved alongside the credentials. If login fails, the link is rejected with HTTP 400.
Credentials and the token are AES-encrypted at rest via IEncryptionService.
Permission required: store.delivery_providers.link
3) Address Mapping (Admin)
Al-Waseet uses numeric city and region IDs. Admins must map Sendy address entities to the corresponding Al-Waseet IDs before orders can be pushed.
PUT api/v1/admin/delivery-providers/address-mapping/provinces/{code}
body: { "alWaseetCityId": 1 }
PUT api/v1/admin/delivery-providers/address-mapping/areas/{areaId}
body: { "alWaseetRegionId": 101 }
Stored on:
AddressProvince.AlWaseetCityId(int?) — maps a Sendy province (e.g.,"BGH") to its Al-Waseetcity_idAddressArea.AlWaseetRegionId(int?) — maps a Sendy area (district/neighborhood) to its Al-Waseetregion_id
Obtain the correct city/region IDs from the Al-Waseet merchant dashboard or their API documentation.
Permission required: admin.delivery_providers.manage
4) Order Auto-Push Flow
When a Sendy order is created and the store has an active Al-Waseet link, StoreDeliveryProviderService.TryPushOrderAsync is called automatically. The push only fires when order.DeliveryCompanyId points to the Al-Waseet delivery company record — orders assigned to a Boxy, Hi-Express, or Sendy-internal DC are routed to their respective provider (or skipped if no external link exists). Use PreferredDeliveryCompanyId on order create to pin a specific DC.
The push flow:
- Resolves
AlWaseetCityIdfromAddressProvincematchingorder.AddressProvinceCode. - Resolves
AlWaseetRegionIdfromAddressAreamatchingorder.AddressAreaId. - If either mapping is missing (
cityId == 0orregionId == 0), the push is recorded asFailedwith a descriptive error — the order is not discarded, only the external delivery record is marked failed. - Calls
EnsureFreshTokenAsync— uses the cached token if less than 23 hours old, otherwise re-authenticates with Al-Waseet and saves the new token. - Calls
IAlWaseetClient.CreateOrderAsyncwith the following fields:
| Al-Waseet field | Source |
|---|---|
client_name | order.CustomerName |
client_mobile | order.CustomerPhone |
city_id | AddressProvince.AlWaseetCityId |
region_id | AddressArea.AlWaseetRegionId |
location | order.CustomerAddress |
type_name | order.PackageType (default: "standard") |
items_number | 1 (fixed) |
price | order.OrderValue (integer) |
package_size | 1 (fixed) |
replacement | 0 (fixed — no replacement by default) |
merchant_notes | order.Notes (optional) |
- On success, the Al-Waseet
qr_idis written toOrderExternalDelivery.ExternalOrderIdand theqr_link(if returned) can be used to access the shipment label.
OrderExternalDelivery fields after a successful push:
| Field | Value |
|---|---|
ExternalOrderId | Al-Waseet qr_id |
ExternalStatusId | integer status code |
ExternalStatusLabel | human-readable status string |
PushStatus | Success |
PushedAt | UTC timestamp of the successful push |
5) Status Sync
Al-Waseet does not push webhooks — status must be polled manually:
POST api/v1/store/delivery-providers/orders/{orderId}/sync-status
This calls IAlWaseetClient.GetOrderStatusAsync with the stored token and qr_id, then updates OrderExternalDelivery.ExternalStatusId and ExternalStatusLabel.
Status Codes
| ID | Enum | Meaning |
|---|---|---|
| 1 | Active | Order created, awaiting driver |
| 2 | ReceivedByDriver | Picked up by driver |
| 3 | InDelivery | Out for delivery |
| 4 | DeliveredToCustomer | Delivered successfully |
| 5 | AtBaghdadSortFacility | At sorting facility (inter-city) |
| 7 | EnRouteToProvince | In transit to destination province |
| 16 | ReturnInProgress | Return initiated |
| 27 | Closed | Order closed |
| 29 | Deferred | Delivery deferred |
6) Token Management
Al-Waseet session tokens are valid for approximately 24 hours. Sendy treats them as stale after 23 hours (TokenTtl) to avoid expiry during a push.
- On link creation: token is fetched and stored.
- On every order push or status sync:
EnsureFreshTokenAsyncchecksTokenFetchedAt. If stale, it re-authenticates, encrypts the new token, updatesTokenFetchedAt, and saves to the database before proceeding. - If re-authentication fails, the push/sync is aborted and an error is logged.
7) Error Cases
| Condition | Outcome |
|---|---|
Province mapping missing (AlWaseetCityId == null) | Push marked Failed; order saved normally |
Area mapping missing (AlWaseetRegionId == null) | Push marked Failed; order saved normally |
| Token refresh fails | Push/sync aborted; HTTP 502 returned on sync |
Al-Waseet API returns status: false | Push marked Failed with Al-Waseet error message |
| No external delivery record found | Sync returns HTTP 404 |
| Order not yet pushed successfully | Sync returns HTTP 400 |
8) Typical Setup Flow
- Admin: create the Al-Waseet delivery company record via
POST api/v1/admin/delivery-providerswithslug = "al-waseet". - Admin: map all active provinces via
PUT api/v1/admin/delivery-providers/address-mapping/provinces/{code}— one call per province with the correct Al-Waseetcity_id. - Admin: map all active areas via
PUT api/v1/admin/delivery-providers/address-mapping/areas/{areaId}— one call per area with the correct Al-Waseetregion_id. - Store owner: link their Al-Waseet account via
POST api/v1/store/delivery-providers/linkswithdeliveryCompanyIdpointing to the Al-Waseet record, plus their Al-Waseetusernameandpassword. The system validates credentials on link creation. - Sendy order workflow: when a Sendy order is assigned to the Al-Waseet DC, the system auto-pushes it. The
OrderExternalDeliveryrecord tracks theqr_idand status. - Status updates: call
POST api/v1/store/delivery-providers/orders/{orderId}/sync-statusperiodically or on demand to pull the latest status from Al-Waseet.
9) Al-Waseet API Contract
Base URL https://api.alwaseet-iq.net/v1/merchant/ is configured in the AlWaseetClient HttpClient registration. All POST bodies use multipart/form-data; every endpoint except login takes the session token as a token query parameter. Responses share the envelope { "status": true|false, "errNum": "...", "msg": "...", "data": ... } — on status: false the error message is in msg. Al-Waseet rate-limits all endpoints to 30 requests per 30 seconds per user. The client covers the full official API (docs):
| Endpoint | Method | Client method | Notes |
|---|---|---|---|
/login | POST | LoginAsync | username + password form fields → data.token |
/citys | GET | GetCitiesAsync | data: [{ id, city_name }] |
/regions?city_id=<int> | GET | GetRegionsAsync | data: [{ id, region_name }] |
/package-sizes | GET | GetPackageSizesAsync | data: [{ id, size }] |
/statuses | GET | GetStatusesAsync | data: [{ id, status }] — full status-code catalog |
/create-order | POST | CreateOrderAsync | order form fields (below) → qr_id, qr_link |
/edit-order | POST | EditOrderAsync | same fields as create plus qr_id; only while order is still with the merchant |
/merchant-orders | GET | GetMerchantOrdersAsync | all orders for the merchant account |
/get-orders-by-ids-bulk | POST | GetOrdersByIdsAsync | ids comma-separated form field, max 25 per call (client enforces) |
/check-order?qr_id=<id> | GET | GetOrderStatusAsync | legacy single-order status check — no longer in the official docs |
/get_merchant_invoices | GET | GetMerchantInvoicesAsync | data: [{ id, merchant_price, delivered_orders_count, replacement_delivered_orders_count, status, merchant_id, updated_at }] |
/get_merchant_invoice_orders?invoice_id=<id> | GET | GetMerchantInvoiceOrdersAsync | data: { invoice: [...], orders: [...] } |
/receive_merchant_invoice?invoice_id=<id> | GET | ReceiveMerchantInvoiceAsync | marks the invoice as received by the merchant |
Order form fields (create/edit): client_name, client_mobile (+964… format), client_mobile2 (optional), city_id, region_id, location, type_name, items_number, price, package_size, replacement (0/1), merchant_notes (optional).
10) Store Management API
Once a store has linked an Al-Waseet account, the following JWT-authenticated endpoints proxy Al-Waseet API calls on behalf of the store. All routes are under api/v1/store/ and require role store_owner | store_staff.
Lookups
| Method | Route | Permission | Description |
|---|---|---|---|
GET | al-waseet/cities | View | List all Al-Waseet cities |
GET | al-waseet/regions?cityId={id} | View | List regions for a city |
GET | al-waseet/package-sizes | View | List available package sizes |
GET | al-waseet/statuses | View | List all status codes with labels |
Use GET al-waseet/cities + GET al-waseet/regions?cityId=… to discover the correct city_id / region_id values for the admin address mapping endpoints (§3).
Orders
| Method | Route | Permission | Description |
|---|---|---|---|
GET | al-waseet/orders | View | All orders for this merchant account |
POST | al-waseet/orders/by-ids | View | Fetch up to 25 orders by QR ID |
PATCH | al-waseet/orders/{qrId} | Link | Edit an order (only while still with merchant) |
POST al-waseet/orders/by-ids body:
{ "ids": ["qr1", "qr2"] }
PATCH al-waseet/orders/{qrId} body: same fields as AlWaseetCreateOrderRequest (clientName, clientMobile, cityId, regionId, location, typeName, itemsNumber, price, packageSize, replacement, merchantNotes).
Invoices
| Method | Route | Permission | Description |
|---|---|---|---|
GET | al-waseet/invoices | View | List all merchant invoices |
GET | al-waseet/invoices/{invoiceId} | View | Invoice detail with its orders |
POST | al-waseet/invoices/{invoiceId}/receive | Link | Mark an invoice as received |
Error Cases
| Condition | HTTP | Message |
|---|---|---|
| No Al-Waseet link for this store | 404 | "No active Al-Waseet account linked to this store." |
| Token refresh fails | 502 | "Al-Waseet returned an error." |
Al-Waseet API returns status: false | 502 | Al-Waseet error message |
Source References
| Layer | File |
|---|---|
| Controller | Sendy.Api/Controllers/V1/Store/StoreAlWaseetController.cs |
| Store service interface | Sendy.Application/Interfaces/Services/StoreArea/IAlWaseetStoreService.cs |
| Store service implementation | Sendy.Application/Services/StoreArea/AlWaseetStoreService.cs |
11) Source References
| Layer | File |
|---|---|
| HTTP client interface | Sendy.Application/Interfaces/Services/Integrations/IAlWaseetClient.cs |
| HTTP client implementation | Sendy.Infrastructure/Services/External/DeliveryCompanies/AlWaseetClient.cs |
| Store management controller | Sendy.Api/Controllers/V1/Store/StoreAlWaseetController.cs |
| Store service interface | Sendy.Application/Interfaces/Services/StoreArea/IAlWaseetStoreService.cs |
| Store service implementation | Sendy.Application/Services/StoreArea/AlWaseetStoreService.cs |
| Delivery provider link service | Sendy.Application/Services/StoreArea/StoreDeliveryProviderService.cs |
| Order push logic | StoreDeliveryProviderService.PushOrderToAlWaseetAsync |
| Token refresh logic | StoreDeliveryProviderService.EnsureFreshTokenAsync / AlWaseetStoreService.EnsureFreshTokenAsync |
| Status sync logic | StoreDeliveryProviderService.SyncOrderStatusAsync |
| Create order request DTO | Sendy.Application/DTOs/Requests/AlWaseet/AlWaseetCreateOrderRequest.cs |
| Login result DTO | Sendy.Application/DTOs/Responses/AlWaseet/AlWaseetLoginResult.cs |
| Create order result DTO | Sendy.Application/DTOs/Responses/AlWaseet/AlWaseetCreateOrderResult.cs |
| Status result DTO | Sendy.Application/DTOs/Responses/AlWaseet/AlWaseetGetOrderStatusResult.cs |
| Status enum | Sendy.Domain/Enums/Orders/AlWaseetOrderStatus.cs |
| Province mapping field | AddressProvince.AlWaseetCityId — Sendy.Domain/Entities/Location/AddressProvince.cs |
| Area mapping field | AddressArea.AlWaseetRegionId — Sendy.Domain/Entities/Location/AddressArea.cs |
| Admin address mapping endpoints | Sendy.Api/Controllers/V1/Admin/AdminDeliveryProvidersController.cs |
| External delivery tracking | Sendy.Domain/Entities/Orders/OrderExternalDelivery.cs |
Source: DOCS/flows/al-waseet-delivery-provider-flow.md