Django 6 + DRF resource server against the Gooyal accounts OAuth2 service, matching the Winsoo ecosystem's conventions. Covers locations, stores, catalog, cart, checkout/orders (with the multi-store-cart split and the status stepper), reviews, and notifications, plus a demo-data seed command. Payment integration (wallet debits, online gateway, seller payouts) is intentionally left out here — see feature/payment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
28 KiB
Winofy API — Frontend Integration Guide
This document is written for whoever (human or AI assistant) is building the two Winofy clients — اپ مشتری (customer app) and پنل فروشنده (seller panel) — against this backend. It covers auth, conventions, every endpoint, and the end-to-end flows that tie them together. Read §3 (Authentication) and §4 (Conventions) first — everything else assumes them.
Backend repo: winofy-backend (Django 6 + DRF). Swagger UI: GET /api/swagger/swagger-ui/. Raw OpenAPI schema: GET /api/swagger/schema/.
Branch note: this guide describes
main, which does not yet include payment integration (wallet debits, the online gateway, seller payouts/withdrawals) — that's onfeature/payment. Onmain,checkoutstill accepts apayment_methodand creates orders normally, butOrderGroup.payment_statusjust stayspendingregardless of method, there's nopaymentkey in the checkout response, and theSeller · Walletendpoints (§7) don't exist yet. Everything else in this doc applies to both branches.
1. Base URL
All endpoints below are relative to /api/. Domain apps live under /api/v1/; health check and OAuth2 live directly under /api/.
{API_BASE_URL}/api/v1/...
2. Two clients, one API, one role model
There is no separate "seller API" service — it's the same backend, same auth, same User. Whether a logged-in user is a "customer" or a "seller" is determined entirely by whether they own a Store:
- Any authenticated user can call the customer-facing endpoints (browse, cart, checkout, etc).
POST /api/v1/seller/store/lets any authenticated user become a seller by creating their store (this is the "ایجاد فروشگاه" screen). One user → at most one store.- Every endpoint under
seller/...requires the user to already own a store (403/404otherwise — see §4.3).
So: the seller panel app should call GET /api/v1/seller/store/ right after login; a 404 means "show the create-store onboarding flow," anything else means "show the dashboard."
3. Authentication
This backend does not implement login, OTP, or signup endpoints. Like every other Winsoo/Gooyal service, it's an OAuth2 resource server: your client authenticates directly against the central Gooyal accounts service and sends the resulting bearer token to Winofy.
Authorization: Bearer <access_token issued by Gooyal accounts>
- The phone-number + OTP screens in the Figma file (C01/C02 for customers, S01/S02 for sellers) are a UI over Gooyal's own
/oauth2/token/endpoint (password/OTP grant) — not a Winofy endpoint. Get the exact request shape and base URL from whoever owns the Gooyal accounts service integration for this app. - Winofy will lazily create a local shadow user (keyed by the same UUID Gooyal uses) the first time it sees a valid token for that user — the frontend doesn't need to do anything to "register" a user with Winofy.
- There is no refresh-token handling documented here; follow the standard OAuth2 refresh flow against Gooyal accounts.
3.1 Local development without real Gooyal credentials
If you're integrating against a local instance that doesn't have real Gooyal OAuth2 credentials wired up yet, the two workarounds are:
- Django session auth (also enabled): log into
/api/admin/with a superuser account in a browser, then a browser-based dev client sharing cookies can call the API using that session. Not viable for a mobile app or a separate origin without CORS+CSRF setup. - Ask backend/DevOps for a real Gooyal
client_id/client_secretand a test phone number — this is the only realistic path for end-to-end testing outside a browser.
There is no dev-mode bypass endpoint in this API — don't build against one that doesn't exist.
4. Conventions
4.1 Success responses — plain, not wrapped
Successful responses are the serializer's JSON directly. There is no {success: true, data: ...} envelope — that only applies to errors (§4.2). Don't unwrap a data key that isn't there.
4.2 Error responses — always wrapped, Persian messages
Every non-2xx response has this shape:
{
"success": false,
"status_code": 400,
"status_message": "Bad Request",
"details": {
"message": { "field_name": ["پیام خطا به فارسی"] },
"error": "invalid",
"timestamp": "2026-08-09T12:00:00.000000+00:00"
}
}
details.messageis either a string, or an object keyed by field name (validation errors) — check which shape you got before rendering.- All user-facing error text is in Persian — show
details.messagedirectly to the user, don't re-translate. - Common status codes you'll see:
400(validation),401(missing/invalid token),403(authenticated but not allowed — e.g. non-seller hitting a seller endpoint's permission check),404(not found / not yours),409(conflict — e.g. re-creating a store, invalid status transition),503(an upstream Gooyal/payment-gateway call failed).
4.3 Seller-endpoint permission errors
seller/* endpoints use two different failure modes depending on the view — be ready for either:
403 Forbidden(from theIsStoreOwnerpermission check) on mostseller/*list/action endpoints.404 Not Foundspecifically fromGET/PATCH /seller/store/before a store exists (it does a plainget_object_or_404, not a permission check, since "you don't have a store yet" is the expected first-run state).
4.4 Pagination
List endpoints use limit/offset pagination:
GET /api/v1/stores/?limit=20&offset=40
{ "count": 132, "next": "http://.../stores/?limit=20&offset=60", "previous": "http://.../stores/?limit=20&offset=20", "results": [ ... ] }
Default page size is 50 if limit is omitted.
4.5 Identifiers, money, dates
- Every object's primary key is a UUID string (field name
uuid), not an integer. Use it in URLs:/api/v1/products/{uuid}/. - All money fields (
price,delivery_fee,items_subtotal,total_amount,amount, etc.) are integers in Toman, no decimals, no currency string. - All timestamps are ISO 8601 with timezone (
created_at,placed_at,scheduled_at, ...). - Image fields (
image,logo,cover_image,icon) are eithernullor an absolute/relative media URL — never assume they're set.
4.6 Enums
| Field | Values | Persian label |
|---|---|---|
Address.label |
home, work, other |
خانه, محل کار, سایر |
Product.unit_type |
gram, ml, piece |
گرم, میلیلیتر, عدد |
Store.status |
pending, approved, suspended |
در انتظار تایید, تایید شده, معلق شده |
StoreWorkingHours.weekday |
0–6 |
شنبه=0 … جمعه=6 (Iranian week, not Sun-Sat) |
OrderGroup.delivery_type |
express, scheduled |
ارسال فوری, زمانبندی شده |
OrderGroup.payment_method |
wallet, online, cash_on_delivery |
کیف پول وینسو, درگاه بانکی, پرداخت در محل |
OrderGroup.payment_status |
pending, paid, failed |
در انتظار پرداخت, پرداخت شده, ناموفق |
Order.status |
placed, preparing, ready_to_ship, handed_to_courier, delivered, cancelled |
سفارش ثبت شد → در حال آمادهسازی → آماده ارسال → تحویل سفیر شد → تحویل داده شد (+ لغو شده) |
SellerWithdrawalRequest.status |
pending, processing, paid, rejected |
در انتظار بررسی, در حال پردازش, واریز شده, رد شده |
WalletTransactionRef.direction |
debit, credit |
برداشت, واریز |
Notification.type |
new_order, settlement_done, new_review, order_cancelled |
سفارش جدید, تسویه حساب, نظر جدید, لغو سفارش |
Order.status only moves forward through that exact sequence (or to cancelled from placed/preparing only) — see §6.3.
5. Customer app — endpoint reference
Auth column: Public = no token needed, Auth = any logged-in user, all scoped to the requester unless noted.
Locations (Locations / Addresses in swagger)
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /api/v1/cities/ |
Public | List active cities. |
| GET | /api/v1/neighborhoods/?city={uuid} |
Public | List neighborhoods, optionally filtered by city. |
| GET | /api/v1/addresses/ |
Auth | The caller's own saved addresses. |
| POST | /api/v1/addresses/ |
Auth | Create an address (see body below). |
| GET/PATCH/DELETE | /api/v1/addresses/{uuid}/ |
Auth |
Address create/update body:
{
"label": "home",
"title": "",
"full_address": "خیابان ولیعصر، کوچه ۱۲",
"plaque": "15", "floor": "2", "unit": "3",
"recipient_name": "سارا احمدی", "recipient_phone": "09123456789",
"city_uuid": "<city uuid>",
"neighborhood_uuid": "<neighborhood uuid or omit>",
"latitude": 35.7595, "longitude": 51.4088,
"is_default": true
}
Response mirrors this but with city/neighborhood as nested objects (not *_uuid), plus uuid and created_at.
Stores (Stores tag)
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /api/v1/store-categories/ |
Public | سوپرمارکت / کافه / رستوران / ... |
| GET | /api/v1/stores/?neighborhood={uuid}&category={uuid}&search=text&lat=..&lng=.. |
Public | Home feed / search. lat+lng sorts by distance (15 km radius). Only status=approved stores are ever returned. |
| GET | /api/v1/stores/{uuid}/ |
Public | Store page — includes working_hours, accepts_wallet/online/cash_on_delivery, description, address, phone_number (fields the list endpoint omits). |
Store list item shape: uuid, name, category{uuid,name,icon,order}, logo, cover_image, rating_avg, rating_count, min_order_amount, delivery_fee, free_delivery_threshold, is_open.
Catalog (Catalog tag)
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /api/v1/product-categories/ |
Public | Flat shared taxonomy (parent nullable for subcategories). |
| GET | /api/v1/products/?store={uuid}&category={uuid}&search=text |
Public | Store page's product grid / search. Only active products of approved stores. |
| GET | /api/v1/products/{uuid}/ |
Public | Product detail — adds description, category, store (a StoreListSerializer). |
Product list item shape: uuid, name, image, price, unit_type, unit_value, is_active, is_out_of_stock.
Cart (Cart tag) — Auth required for all
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/cart/ |
Returns the cart grouped by store — see shape below. |
| DELETE | /api/v1/cart/ |
Empties the whole cart. |
| POST | /api/v1/cart/items/ |
Add a product; if it's already in the cart, quantities add together (not replaced). Body: {"product_uuid": "...", "quantity": 2}. |
| PATCH | /api/v1/cart/items/{item_uuid}/ |
Set an absolute quantity: {"quantity": 5}. |
| DELETE | /api/v1/cart/items/{item_uuid}/ |
Remove one line. |
GET /api/v1/cart/ response — this is the shape behind the multi-store-cart UI (C07):
{
"groups": [
{
"store": { "uuid": "...", "name": "سوپرمارکت پارسیان", "...": "..." },
"items": [
{ "uuid": "<cart item uuid>", "product": { "uuid": "...", "name": "شیر کاله", "price": 28500, "...": "..." }, "quantity": 2, "line_total": 57000 }
],
"items_subtotal": 57000,
"delivery_fee": 15000,
"total": 72000,
"meets_minimum_order": true
}
],
"grand_total": 72000
}
meets_minimum_order: falsemeans this store's slice is belowstore.min_order_amount— checkout will reject it (§6.2). Surface this in the cart UI per-store, same as the mock.delivery_feeis already0here if the store'sfree_delivery_thresholdwas met — don't recompute it client-side.
Checkout (Checkout tag) — Auth required
| Method | Path |
|---|---|
| POST | /api/v1/checkout/ |
Body:
{
"address_uuid": "<address uuid>",
"delivery_type": "express",
"scheduled_at": null,
"payment_method": "wallet",
"notes": ""
}
scheduled_atis required (ISO datetime) whendelivery_typeis"scheduled"— omitting it is a400.address_uuidmust belong to the caller, or400.- The cart is split into one
Orderper store and cleared on success. See §6.1–§6.2 for the full flow including coverage checks and payment-method branching.
Response = an OrderGroup object (§5, Orders below):
{
"uuid": "<order group uuid>",
"recipient_name": "...", "recipient_phone": "...", "full_address": "...",
"delivery_type": "express", "scheduled_at": null,
"payment_method": "online", "payment_status": "pending",
"notes": "", "total_amount": 178500,
"orders": [ /* array of Order objects, one per store — see Orders below */ ],
"created_at": "..."
}
On main, that's it — payment_status stays "pending" no matter which payment_method you send; nothing external is contacted. On feature/payment, the response also includes a payment key and payment_status actually reflects what happened:
wallet:payment={"method": "wallet", "status": "paid"}— synchronous, done.online:payment={"method": "online", "status": "pending", "payment_url": "..."}— redirect/open a webview atpayment_url; the gateway calls Winofy back and payment_status updates asynchronously (pollGET /api/v1/order-groups/{uuid}/orGET /api/v1/orders/{uuid}/to see it flip topaid).cash_on_delivery:payment={"method": "cash_on_delivery", "status": "pending"}— nothing to do, collected on delivery.
Orders (Orders tag) — Auth required, scoped to the caller
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/order-groups/ |
Order history — each entry may bundle several stores' orders from one checkout. |
| GET | /api/v1/order-groups/{uuid}/ |
|
| GET | /api/v1/orders/{uuid}/ |
Single-order tracking (C09) — one store's slice. |
| POST | /api/v1/orders/{uuid}/cancel/ |
Body: {"reason": ""} (optional). Only works while is_cancellable is true. |
Order object shape (nested inside OrderGroup.orders, or standalone from GET /orders/{uuid}/):
{
"uuid": "...", "store": { "...": "StoreListSerializer" }, "status": "preparing",
"items": [
{ "uuid": "...", "product": "<product uuid>", "product_name_snapshot": "شیر کاله", "unit_price_snapshot": 28500, "quantity": 2, "line_total": 57000 }
],
"items_subtotal": 57000, "delivery_fee": 15000,
"commission_amount": 2280, "seller_payout_amount": 54720,
"total_amount": 72000,
"cancel_reason": "", "courier_name": "", "courier_phone": "",
"placed_at": "...", "delivered_at": null,
"status_logs": [ { "from_status": "", "to_status": "placed", "note": "", "created_at": "..." } ],
"is_cancellable": true
}
items[].productis just the product UUID (not expanded) — useproduct_name_snapshot/unit_price_snapshotfor display; they're frozen at order time and won't change even if the product is later edited or deleted.- Drive the order-tracking stepper UI off
status_logs(ordered oldest→newest) or just offstatusdirectly — both are provided. commission_amount/seller_payout_amountare platform-internal — fine to show in a seller-facing context, not normally shown to the customer.
Reviews (Reviews tag)
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /api/v1/reviews/?store={uuid} |
Public | Reviews for a store's page. |
| POST | /api/v1/reviews/ |
Auth | Body: {"order_uuid": "...", "rating": 5, "comment": "..."}. Only allowed once order.status == "delivered", and only once per order (400 otherwise — see error messages in §4.2). |
Response adds customer_name, store (uuid), product (uuid, nullable), seller_reply (null until the seller replies).
Notifications (Notifications tag) — Auth required, shared by both apps
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/notifications/ |
The caller's notifications (works the same whether they're a customer or a seller — it's just "notifications for this user"). |
| GET | /api/v1/notifications/{uuid}/ |
|
| POST | /api/v1/notifications/{uuid}/mark-read/ |
|
| POST | /api/v1/notifications/mark-all-read/ |
Returns 204. |
Shape: uuid, type, title, body, related_order (uuid, nullable), is_read, created_at. All fields are read-only from the client's perspective (server-generated).
6. Key flows
6.1 Location & delivery-coverage flow (the neighborhood-confirmation-sheet)
Stores don't deliver everywhere — each Store has a service_neighborhoods list. The Figma "محلهت رو انتخاب کن" sheet exists because of this. The API enforces it at checkout time, not earlier — the client's job is to react to the failure, not to pre-validate:
- Customer picks/has an
Address(with aneighborhood_uuid) — via the address list, or the map picker +POST /api/v1/addresses/. - Customer browses/adds to cart freely —
GET /api/v1/stores/and cart endpoints do not check coverage. - On
POST /api/v1/checkout/, if any store in the cart doesn't serve the address's neighborhood, the whole checkout fails with400and a message like "فروشگاه «X» به محله انتخابی شما ارسال ندارد." — show the confirmation sheet / prompt the user to pick a different address at that point.
If you want to warn the customer earlier (e.g. on the store page), you'd need to compare the store's service_neighborhoods (not currently exposed on the customer-facing store serializer — ask backend to add it if you need this pre-check) against the customer's selected/default address neighborhood.
6.2 Multi-store cart → checkout
GET /api/v1/cart/→ render one card pergroups[]entry (matches C07).- Block the "ادامه و ثبت سفارش" button per-store (or overall) if any group has
meets_minimum_order: false. POST /api/v1/checkout/once. The backend, inside one transaction:- re-validates minimum order, stock, and neighborhood coverage per store (can still fail here even if the cart screen looked fine, e.g. a race with another customer depleting stock) — handle the
400gracefully; - creates one
OrderGroup+ oneOrderper store, snapshots the address intorecipient_name/recipient_phone/full_addresson the group (so later address edits don't retroactively change past orders); - decrements
Product.stock_quantityand clears the cart; - branches on
payment_method(§5 Checkout).
- re-validates minimum order, stock, and neighborhood coverage per store (can still fail here even if the cart screen looked fine, e.g. a race with another customer depleting stock) — handle the
- Route based on
payment.method:wallet/cash_on_delivery→ go straight to an order-confirmation screen.online→ openpayment.payment_url, then poll or listen for the order group to reachpayment_status: "paid".
6.3 Order status stepper (seller side drives it, customer side reads it)
The seller panel is the only side that advances status. Valid transitions:
placed → preparing → ready_to_ship → handed_to_courier → delivered
placed → cancelled
preparing → cancelled
No other transition is allowed — the backend returns 409 for anything else (e.g. trying to jump from placed straight to delivered, or cancelling after handed_to_courier). See §7 for the seller-side action endpoints. The customer app's POST /orders/{uuid}/cancel/ uses the same rule (only while placed/preparing).
Commission (Order.commission_amount) is fixed at checkout time from the store's rate (4% by default) applied to items_subtotal only — delivery_fee is excluded from the split and goes to the seller, on both branches. On feature/payment only, the delivered transition additionally credits the seller's Gooyal wallet and fires a settlement_done notification — none of this needs client involvement either way. On main, delivered just marks the order delivered; no money moves yet.
6.4 Payment methods, in full (feature/payment only)
main accepts and stores payment_method on checkout but doesn't act on it — payment_status stays pending regardless. The behavior below is what feature/payment adds:
payment_method |
What happens | Client follow-up |
|---|---|---|
wallet |
Debits the customer's Gooyal wallet synchronously during checkout. | None — payment_status is already paid in the checkout response. |
online |
Opens a charge request on the internal ipg gateway. |
Redirect to payment.payment_url; the gateway calls Winofy back asynchronously. Poll the order/order-group for payment_status. |
cash_on_delivery |
Nothing charged now. | Show "پرداخت در محل" confirmation; money changes hands at delivery, outside the API. |
7. Seller panel — endpoint reference
Every endpoint below requires the caller to own a Store (IsStoreOwner), except store creation itself.
Seller · Store
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/seller/store/ |
404 if the user hasn't created a store yet → show onboarding. |
| POST | /api/v1/seller/store/ |
Creates the store (one per user, 409 on a second attempt). Body below. |
| PATCH | /api/v1/seller/store/ |
Partial update, same body shape. |
| GET | /api/v1/seller/store/working-hours/ |
Returns array of {weekday, opens_at, closes_at, is_closed}. |
| PUT | /api/v1/seller/store/working-hours/ |
Full replace — send all 7 days each time; body is a bare array (or {"working_hours": [...]}). |
Store create/update body:
{
"name": "سوپرمارکت پارسیان",
"category_uuid": "<store category uuid>",
"description": "...",
"phone_number": "09166352131",
"city_uuid": "<city uuid>",
"address": "خیابان آزادی، نبش کوچه مریم",
"latitude": 35.71, "longitude": 51.35,
"service_neighborhood_uuids": ["<neighborhood uuid>", "..."],
"delivery_radius_km": 5,
"min_order_amount": 50000, "delivery_fee": 25000, "free_delivery_threshold": null,
"accepts_wallet": true, "accepts_online": true, "accepts_cash_on_delivery": true,
"is_open": true
}
rating_avg, rating_count, status are read-only (server-computed; status starts pending — there's no admin-approval endpoint in this API yet, that's a manual/admin-panel step today). logo/cover_image aren't settable through this JSON body — that needs a multipart upload endpoint, which doesn't exist yet; flag this to backend if the store-branding screen needs it.
Seller · Products
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/seller/products/ |
Only the caller's own products. |
| POST | /api/v1/seller/products/ |
Create (S07 "افزودن محصول"). |
| GET/PATCH/DELETE | /api/v1/seller/products/{uuid}/ |
|
| PATCH | /api/v1/seller/products/{uuid}/stock/ |
Inventory-only quick update (S08): {"stock_quantity": 12}. |
Product body: name, description, image, category_uuid, price, unit_type, unit_value, stock_quantity, low_stock_threshold, is_active. Response adds sold_count (read-only), is_out_of_stock, is_low_stock (both computed: stock_quantity <= 0, and 0 < stock_quantity <= low_stock_threshold).
Seller · Orders
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/seller/orders/?status=placed |
status filter matches the S09 tabs; omit for all. |
| GET | /api/v1/seller/orders/{uuid}/ |
S10 detail. |
| POST | /api/v1/seller/orders/{uuid}/confirm/ |
placed → preparing. |
| POST | /api/v1/seller/orders/{uuid}/mark-ready/ |
preparing → ready_to_ship. |
| POST | /api/v1/seller/orders/{uuid}/mark-shipped/ |
ready_to_ship → handed_to_courier. Body may include courier_name/courier_phone. |
| POST | /api/v1/seller/orders/{uuid}/mark-delivered/ |
handed_to_courier → delivered. Triggers settlement (§6.3). |
| POST | /api/v1/seller/orders/{uuid}/cancel/ |
Only from placed/preparing. Body: {"reason": ""}. |
All the action endpoints accept an optional body {"note": "", "courier_name": "", "courier_phone": ""} and return the updated Order object (§5 shape). A 409 means the transition isn't legal from the order's current status (§6.3) — don't just retry, refetch the order and re-render the correct available actions.
Seller · Wallet (feature/payment only — not on main)
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/seller/wallet/ |
{"withdrawable_balance": 12500000, "pending_settlement": 350000, "recent_transactions": [...]} (S11). |
| GET | /api/v1/seller/wallet/withdrawals/ |
The seller's withdrawal request history. |
| POST | /api/v1/seller/wallet/withdrawals/ |
S12 form: {"amount": 500000, "iban": "IR120120000000012345678", "account_holder_name": "سارا احمدی"}. iban must match ^IR\d{24}$ or 400. |
recent_transactions[] items: uuid, direction ("debit"/"credit"), amount, purpose, status, created_at.
Seller · Analytics
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/seller/analytics/?period=today |
period ∈ today/week/month (default today). S17 dashboard. |
Response:
{
"period": "today",
"order_count": 12, "total_sales": 2450000,
"cancellation_rate": 4.5, "average_order_value": 204000,
"chart": [ { "bucket": "2026-08-09T09:00:00Z", "sales": 320000 } ],
"top_products": [ { "name": "شیر کم چرب کاله 1 لیتری", "units_sold": 44, "revenue": 1680000 } ]
}
chart[].bucket is hourly when period=today, daily otherwise. top_products is capped at 5, sorted by revenue.
Seller · Reviews
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/seller/reviews/ |
Reviews on the caller's store. |
| POST | /api/v1/seller/reviews/{uuid}/reply/ |
{"seller_reply": "ممنون از خرید شما"}. |
Payments webhook (feature/payment only — not on main, and not called by either client app)
GET/POST /api/v1/payments/ipg/callback/ is hit by the ipg payment gateway service itself after a customer completes an online payment, not by the frontend. Don't call it directly; it's documented here only so you know it exists and isn't a client-facing endpoint.
8. Local development / testing
- The backend ships a management command that seeds a realistic dataset: 3 cities, 13 neighborhoods, ~19 stores across 6 categories, ~370 products, 30 customers with addresses, 150+ historical orders in every status (placed/preparing/delivered/cancelled), and reviews. Ask backend to run
python manage.py seed_demo_data(or--flushto reset it) against your dev environment before you start wiring up screens — there's no need to hand-create fixtures. - Swagger UI (
/api/swagger/swagger-ui/) is grouped into the same sections as this doc (Locations, Stores, Catalog, Cart, Checkout, Orders, Reviews, Notifications, and theSeller · *groups —Seller · WalletandPaymentsonly appear onfeature/payment) — use it to try requests once you have a token. GET /api/health/needs no auth and is useful as a "is the backend even up" smoke check.
9. Known gaps to flag back to backend if you hit them
- No multipart/image-upload endpoint yet for store logo/cover or product images (JSON
PATCHbodies can't set binary fields). - No store-approval endpoint — new stores sit in
status: "pending"until changed via Django admin. - No admin/public endpoint to see a store's
service_neighborhoodsfrom the customer-facing store serializer (needed if you want to pre-warn about delivery coverage before checkout — see §6.1). - The
ipgonline-payment callback contract (/api/v1/payments/ipg/callback/) is a best-effort integration pending confirmation against a liveipgenvironment — if online payments seem to hang inpending, that's the first place to check.