
# Orders

Customers place orders in WhatsApp; you read them here and receive them as [events](https://kibanda.africa/docs/events).

## The order object

```json
{
  "id": "ord_…",
  "object": "order",
  "number": 128,
  "status": "confirmed",
  "source": "whatsapp",
  "currency": "KES",
  "subtotal": 28000,
  "delivery_fee": 3000,
  "total": 31000,
  "payment": {
    "mode": "pay_now",
    "status": "paid",
    "amount_paid": 31000,
    "amount_due": 0,
    "upfront_due": 31000,
    "upfront_required_by": null,
    "paid_at": 1792716000,
    "attempts": [{ "id": "pay_…", "method": "mpesa", "amount": 31000, "status": "paid", "receipt": "TJK3H2K9LP", "created": 1792715950 }]
  },
  "items": [{ "product": "pr_…", "product_external_id": "SKU-1001", "name": "Unga 2kg", "quantity": 1, "unit_price": 25000, "amount": 25000 }],
  "customer": { "id": "ct_…", "object": "customer", "name": "Achieng Otieno", "phone": "+254712345678", "external_id": "res.partner/4211", … },
  "fulfilment": "delivery",
  "delivery": {
    "location": { "lat": -1.2921, "lng": 36.7856 },
    "landmark": "Blue gate opposite the church",
    "instructions": "Call when you're at the gate",
    "distance_km": 3.4,
    "status": "picked_up",
    "rider": { "id": "rd_…", "name": "Kevin" },
    "eta_minutes": 14,
    "failure_reason": null
  },
  "pickup": null,
  "attribution": { "source": "ad", "sourceId": "120211…", "headline": "Unga deal", "ctwaClid": "ARAk…" },
  "rating": null,
  "cancel_reason": null,
  "external_id": null,
  "metadata": {},
  "created": 1792715900,
  "updated": 1792716000
}
```

### Status

| `status` | |
|---|---|
| `awaiting_payment` | Pay-now order waiting for payment. |
| `confirmed` | Paid, or pay-on-delivery accepted. The shop should prepare it. |
| `dispatched` | With a rider (see `delivery.status`: `assigned` → `picked_up` → `arrived`). |
| `delivered` | Done. |
| `ready` / `collected` | Pickup orders. |
| `failed` | Delivery failed; stock returned. |
| `cancelled` | See `cancel_reason`. |

`payment.status` is separate: `unpaid`, `pending` (a prompt is out), `partial` (a deposit is in), `paid`. `upfront_due` is what must be paid before the order goes ahead — see [payment terms](https://kibanda.africa/docs/payments#payment-terms-the-shop-decides). A pay-on-delivery order can be `delivered` and `paid` with `method: "cash"`.

## Retrieve and list

- `GET /v1/orders/{id}`
- `GET /v1/orders` — filters: `status`, `payment_status`, `customer` (a customer id), `number`, `source` (see [Ads & attribution](https://kibanda.africa/docs/ads)), `external_id`, `created_gte`, `created_lt`.
- `GET /v1/orders/{id}/events` — the order's full history (who did what, when).

## Link to your system

`POST /v1/orders/{id}` accepts only `external_id` and `metadata` — e.g. after creating the sale order in Odoo:

```bash
curl $KIBANDA_API/v1/orders/ord_123 -H "Authorization: Bearer $KIBANDA_KEY" \
  -H "Content-Type: application/json" -d '{"external_id": "SO0042", "metadata": {"odoo_id": "42"}}'
```

## Ask for payment first

`POST /v1/orders/{id}/require_payment` with `{"amount": "full"}` (default) or `{"amount": "deposit"}` — the shop's call on a confirmed order that hasn't left. The customer is asked on WhatsApp; the order waits until it's paid.

## Cancel

`POST /v1/orders/{id}/cancel` with an optional `reason` (shown to the customer). Stock is returned and a rider who's on it is told to bring it back. Paid orders and orders with a payment in progress return `409 order_not_cancellable`.
