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: StoreDeliveryProviderLinkUsernameEncrypted = Al-Waseet username, PasswordEncrypted = Al-Waseet password. TokenEncrypted/TokenFetchedAt = session token (auto-refreshed every 23 hours).
  • Dispatch discriminator: any linked delivery company whose Slug is 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-Waseet city_id
  • AddressArea.AlWaseetRegionId (int?) — maps a Sendy area (district/neighborhood) to its Al-Waseet region_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:

  1. Resolves AlWaseetCityId from AddressProvince matching order.AddressProvinceCode.
  2. Resolves AlWaseetRegionId from AddressArea matching order.AddressAreaId.
  3. If either mapping is missing (cityId == 0 or regionId == 0), the push is recorded as Failed with a descriptive error — the order is not discarded, only the external delivery record is marked failed.
  4. Calls EnsureFreshTokenAsync — uses the cached token if less than 23 hours old, otherwise re-authenticates with Al-Waseet and saves the new token.
  5. Calls IAlWaseetClient.CreateOrderAsync with the following fields:
Al-Waseet fieldSource
client_nameorder.CustomerName
client_mobileorder.CustomerPhone
city_idAddressProvince.AlWaseetCityId
region_idAddressArea.AlWaseetRegionId
locationorder.CustomerAddress
type_nameorder.PackageType (default: "standard")
items_number1 (fixed)
priceorder.OrderValue (integer)
package_size1 (fixed)
replacement0 (fixed — no replacement by default)
merchant_notesorder.Notes (optional)
  1. On success, the Al-Waseet qr_id is written to OrderExternalDelivery.ExternalOrderId and the qr_link (if returned) can be used to access the shipment label.

OrderExternalDelivery fields after a successful push:

FieldValue
ExternalOrderIdAl-Waseet qr_id
ExternalStatusIdinteger status code
ExternalStatusLabelhuman-readable status string
PushStatusSuccess
PushedAtUTC 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

IDEnumMeaning
1ActiveOrder created, awaiting driver
2ReceivedByDriverPicked up by driver
3InDeliveryOut for delivery
4DeliveredToCustomerDelivered successfully
5AtBaghdadSortFacilityAt sorting facility (inter-city)
7EnRouteToProvinceIn transit to destination province
16ReturnInProgressReturn initiated
27ClosedOrder closed
29DeferredDelivery 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: EnsureFreshTokenAsync checks TokenFetchedAt. If stale, it re-authenticates, encrypts the new token, updates TokenFetchedAt, and saves to the database before proceeding.
  • If re-authentication fails, the push/sync is aborted and an error is logged.

7) Error Cases

ConditionOutcome
Province mapping missing (AlWaseetCityId == null)Push marked Failed; order saved normally
Area mapping missing (AlWaseetRegionId == null)Push marked Failed; order saved normally
Token refresh failsPush/sync aborted; HTTP 502 returned on sync
Al-Waseet API returns status: falsePush marked Failed with Al-Waseet error message
No external delivery record foundSync returns HTTP 404
Order not yet pushed successfullySync returns HTTP 400

8) Typical Setup Flow

  1. Admin: create the Al-Waseet delivery company record via POST api/v1/admin/delivery-providers with slug = "al-waseet".
  2. Admin: map all active provinces via PUT api/v1/admin/delivery-providers/address-mapping/provinces/{code} — one call per province with the correct Al-Waseet city_id.
  3. Admin: map all active areas via PUT api/v1/admin/delivery-providers/address-mapping/areas/{areaId} — one call per area with the correct Al-Waseet region_id.
  4. Store owner: link their Al-Waseet account via POST api/v1/store/delivery-providers/links with deliveryCompanyId pointing to the Al-Waseet record, plus their Al-Waseet username and password. The system validates credentials on link creation.
  5. Sendy order workflow: when a Sendy order is assigned to the Al-Waseet DC, the system auto-pushes it. The OrderExternalDelivery record tracks the qr_id and status.
  6. Status updates: call POST api/v1/store/delivery-providers/orders/{orderId}/sync-status periodically 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):

EndpointMethodClient methodNotes
/loginPOSTLoginAsyncusername + password form fields → data.token
/citysGETGetCitiesAsyncdata: [{ id, city_name }]
/regions?city_id=<int>GETGetRegionsAsyncdata: [{ id, region_name }]
/package-sizesGETGetPackageSizesAsyncdata: [{ id, size }]
/statusesGETGetStatusesAsyncdata: [{ id, status }] — full status-code catalog
/create-orderPOSTCreateOrderAsyncorder form fields (below) → qr_id, qr_link
/edit-orderPOSTEditOrderAsyncsame fields as create plus qr_id; only while order is still with the merchant
/merchant-ordersGETGetMerchantOrdersAsyncall orders for the merchant account
/get-orders-by-ids-bulkPOSTGetOrdersByIdsAsyncids comma-separated form field, max 25 per call (client enforces)
/check-order?qr_id=<id>GETGetOrderStatusAsynclegacy single-order status check — no longer in the official docs
/get_merchant_invoicesGETGetMerchantInvoicesAsyncdata: [{ id, merchant_price, delivered_orders_count, replacement_delivered_orders_count, status, merchant_id, updated_at }]
/get_merchant_invoice_orders?invoice_id=<id>GETGetMerchantInvoiceOrdersAsyncdata: { invoice: [...], orders: [...] }
/receive_merchant_invoice?invoice_id=<id>GETReceiveMerchantInvoiceAsyncmarks 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

MethodRoutePermissionDescription
GETal-waseet/citiesViewList all Al-Waseet cities
GETal-waseet/regions?cityId={id}ViewList regions for a city
GETal-waseet/package-sizesViewList available package sizes
GETal-waseet/statusesViewList 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

MethodRoutePermissionDescription
GETal-waseet/ordersViewAll orders for this merchant account
POSTal-waseet/orders/by-idsViewFetch up to 25 orders by QR ID
PATCHal-waseet/orders/{qrId}LinkEdit 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

MethodRoutePermissionDescription
GETal-waseet/invoicesViewList all merchant invoices
GETal-waseet/invoices/{invoiceId}ViewInvoice detail with its orders
POSTal-waseet/invoices/{invoiceId}/receiveLinkMark an invoice as received

Error Cases

ConditionHTTPMessage
No Al-Waseet link for this store404"No active Al-Waseet account linked to this store."
Token refresh fails502"Al-Waseet returned an error."
Al-Waseet API returns status: false502Al-Waseet error message

Source References

LayerFile
ControllerSendy.Api/Controllers/V1/Store/StoreAlWaseetController.cs
Store service interfaceSendy.Application/Interfaces/Services/StoreArea/IAlWaseetStoreService.cs
Store service implementationSendy.Application/Services/StoreArea/AlWaseetStoreService.cs

11) Source References

LayerFile
HTTP client interfaceSendy.Application/Interfaces/Services/Integrations/IAlWaseetClient.cs
HTTP client implementationSendy.Infrastructure/Services/External/DeliveryCompanies/AlWaseetClient.cs
Store management controllerSendy.Api/Controllers/V1/Store/StoreAlWaseetController.cs
Store service interfaceSendy.Application/Interfaces/Services/StoreArea/IAlWaseetStoreService.cs
Store service implementationSendy.Application/Services/StoreArea/AlWaseetStoreService.cs
Delivery provider link serviceSendy.Application/Services/StoreArea/StoreDeliveryProviderService.cs
Order push logicStoreDeliveryProviderService.PushOrderToAlWaseetAsync
Token refresh logicStoreDeliveryProviderService.EnsureFreshTokenAsync / AlWaseetStoreService.EnsureFreshTokenAsync
Status sync logicStoreDeliveryProviderService.SyncOrderStatusAsync
Create order request DTOSendy.Application/DTOs/Requests/AlWaseet/AlWaseetCreateOrderRequest.cs
Login result DTOSendy.Application/DTOs/Responses/AlWaseet/AlWaseetLoginResult.cs
Create order result DTOSendy.Application/DTOs/Responses/AlWaseet/AlWaseetCreateOrderResult.cs
Status result DTOSendy.Application/DTOs/Responses/AlWaseet/AlWaseetGetOrderStatusResult.cs
Status enumSendy.Domain/Enums/Orders/AlWaseetOrderStatus.cs
Province mapping fieldAddressProvince.AlWaseetCityId — Sendy.Domain/Entities/Location/AddressProvince.cs
Area mapping fieldAddressArea.AlWaseetRegionId — Sendy.Domain/Entities/Location/AddressArea.cs
Admin address mapping endpointsSendy.Api/Controllers/V1/Admin/AdminDeliveryProvidersController.cs
External delivery trackingSendy.Domain/Entities/Orders/OrderExternalDelivery.cs

Source: DOCS/flows/al-waseet-delivery-provider-flow.md