Brief frontend DiddiFood — 2026-10-30
Ce document est le brief frontend courant pour le client DiddiFood et l'espace
restaurant Pro/Cuisine. Le contrat technique de référence reste
docs/api/openapi.json. Les montants sont en XOF et les
dates métier utilisent Africa/Abidjan.
État de validation
Le contrat local OpenAPI a été régénéré et vérifié avec l'application actuelle.
Après le redéploiement du 30 octobre, https://diddifood-backend-staging.diddifree.com/health
et /health/ready retournent 200. Le contrat public vérifié est celui servi
par /openapi.json sur ce même domaine.
1. Règles communes au frontend
URL et authentification
- Définir une seule
DIDDIFOOD_API_BASE_URLpar environnement. En staging, l'URL attendue esthttps://diddifood-backend-staging.diddifree.com. - Envoyer
Authorization: Bearer <JWT DiddiFreeID>sur toutes les routes authentifiées. - Envoyer un
X-Request-IDUUID par opération importante et le conserver dans les logs et les écrans d'erreur Support. - Ne jamais mettre un secret DiddiPay, DiddiFiles ou S2S dans le frontend.
Format
- Les identifiants sont des UUID sauf indication contraire.
- Les montants sont des entiers XOF :
2500signifie 2 500 XOF. Ne pas envoyer de montant flottant et ne pas convertir en centimes d'une autre devise. - Les dates sont ISO 8601 avec fuseau. Afficher les heures métier en
Africa/Abidjan. - Une réponse
503signifie qu'une dépendance backend est indisponible ou non configurée. Afficher un état d'attente ou d'erreur, jamais une réussite. - Après une reconnexion ou une notification, relire la commande, le paiement et la livraison par HTTP.
Portes de déploiement à respecter
DIDDIFOOD_REQUIRE_ONLINE_PAYMENT_BEFORE_PREPARATION=true
DIDDIFOOD_PICKUP_ENABLED=false
DIDDIFOOD_SUBSTITUTION_ENABLED=false
DIDDIFOOD_RESTAURANT_TOPUP_ENABLED=false
Le frontend ne doit pas afficher le retrait sur place, les substitutions ou la
recharge wallet lorsque ces portes sont à false.
2. Parcours client
2.1 Catalogue
- Appeler
GET /food/v1/restaurants. - Paramètres disponibles :
limit(1–100),offset,q,category,is_open,latitude,longitude,radius_km. latitude,longitudeetradius_kmdoivent être envoyés ensemble. Le rayon est en kilomètres et doit être positif, au maximum 10.- La réponse est
RestaurantListOut:items,limit,offset,total. - Une carte restaurant peut utiliser
image_url,rating,review_count,is_open,open_hours,delivery_time_minutesetdelivery_fee_minor_units.image_urlpeut êtrenull.
Catégories actuellement acceptées à la création restaurant : fast_food,
maquis, pizza, traiteur, ivoirien. La recherche par texte est légère
et porte sur les informations indexées par Food.
2.2 Menu
Appeler GET /food/v1/restaurants/{restaurant_id}/menu.
Chaque plat expose notamment id, name, description,
price_minor_units, category, is_available, unavailable_until et
image_url. Une image absente doit afficher un fallback ; Food ne stocke pas
le fichier lui-même, DiddiFiles en reste propriétaire.
2.3 Devis avant commande
Appeler POST /food/v1/restaurants/{restaurant_id}/quote avec :
{
"items": [{"dish_id": "<uuid>", "quantity": 2}],
"pickup_mode": "delivery",
"dropoff_latitude": 5.35,
"dropoff_longitude": -4.01
}
Le serveur renvoie subtotal_minor_units, delivery_fee_minor_units,
total_minor_units, currency, is_open et calculated_at. Le frontend ne
recalcule pas le total final et ne fait pas confiance à un prix conservé dans
le panier.
2.4 Création de commande
Appeler POST /food/v1/restaurants/{restaurant_id}/orders avec :
{
"items": [{"dish_id": "<uuid>", "quantity": 1}],
"payment_method": "cash_on_delivery",
"pickup_mode": "delivery",
"dropoff_address": "Cocody, Abidjan",
"dropoff_latitude": 5.35,
"dropoff_longitude": -4.01,
"recipient_name": "Nom du destinataire",
"recipient_phone": "+2250700000000"
}
Valeurs de paiement : cash_on_delivery ou online. Pour la livraison,
l'adresse, les coordonnées et le contact du destinataire sont nécessaires.
pickup_mode=pickup reste fermé en MVP par
DIDDIFOOD_PICKUP_ENABLED=false.
La réponse contient status=placed, respond_by, les lignes, les totaux et
les horodatages. Le frontend doit afficher le compte à rebours vers
respond_by et traiter l'expiration comme une annulation avec
cancellation_reason=restaurant_unresponsive.
2.5 Paiement en ligne
La règle est : pas de paiement confirmé, pas de préparation.
Flux obligatoire :
- Créer la commande ; elle reste
placed. - Appeler
POST /food/v1/orders/{order_id}/payments/prepareavec{}ou{"customer_email":"..."}. - Lire
PaymentOut.status. - Si
status=requires_action, ouvrirnext_action_urldans le parcours de paiement sécurisé. - Attendre le webhook DiddiPay et relire
GET /food/v1/orders/{order_id}/paymentou appelerPOST /food/v1/orders/{order_id}/payment/refresh. - Le restaurant peut accepter uniquement après
status=succeeded.
customer_email est optionnel. Si le client n'en fournit pas, Food dérive une
valeur technique à partir du téléphone du payeur authentifié. Le téléphone
du destinataire ne doit jamais être utilisé.
États de paiement : pending, requires_action, succeeded, failed,
cancelled, expired, collected_cash, refunded,
partially_refunded. Un retour navigateur ne prouve jamais le paiement à lui
seul.
Pour cash_on_delivery, afficher que la collecte aura lieu dans le parcours
DiddiSend. Le client ne confirme pas lui-même la collecte.
2.6 Suivi de commande et livraison
GET /food/v1/ordersliste les commandes du client (limit,offset).GET /food/v1/orders/{order_id}relit une commande.GET /food/v1/orders/{order_id}/delivery/trackingest la surface stable de suivi frontend.POST /food/v1/orders/{order_id}/delivery/refreshdemande une nouvelle lecture de la projection quand nécessaire.
DeliveryOut expose status, les coordonnées de départ et d'arrivée,
ready_at_estimate, last_synced_at, failure_code et failure_reason, et
peut contenir tracking avec coursier public, position, ETA et dernière mise
à jour.
Les statuts de livraison sont requested, assigned, picked_up,
in_transit, delivered et failed. DiddiSend reste propriétaire du
coursier, de la position, de l'ETA réelle, de la preuve de remise et de la
décision opérationnelle après échec. Food ne doit pas proposer un bouton de
nouvelle tentative ou de remboursement automatique.
2.7 Annulation et remboursement
POST /food/v1/orders/{order_id}/cancelest disponible avant acceptation.POST /food/v1/orders/{order_id}/refund-requestscrée une demande avec unreasontexte obligatoire.GET /food/v1/orders/{order_id}/refund-requestssuit la demande.
Statuts de demande : submitted, approved, rejected, processing,
refunded, failed. Support/Backoffice prend la décision ; DiddiPay exécute
le remboursement, y compris pour une commande initialement cash, vers le
bénéficiaire approprié. Le frontend ne propose jamais un remboursement cash
manuel séparé.
2.8 Notation
- Après
delivered, afficher le formulairePOST /food/v1/orders/{order_id}/ratingavecstarsde 1 à 5 et un commentaire facultatif de 1 000 caractères maximum. - Avant d'afficher le formulaire, appeler
GET /food/v1/orders/{order_id}/rating. 404 RATING_NOT_FOUNDsignifie « aucune note encore donnée », pas une erreur réseau.
2.9 Notifications et temps réel
Le frontend peut enregistrer un appareil avec
POST /food/v1/devices/register et le retirer avec
POST /food/v1/devices/unregister. Les notifications ne remplacent pas la
lecture HTTP. Après reconnexion WebSocket, relire les données de la commande.
3. Parcours restaurant Pro et Cuisine
3.1 Sélection du restaurant et onboarding
GET /food/v1/me/restaurantsrenvoie les restaurants liés à l'utilisateur et leur rôle (manageroukitchen).- Le MVP conserve l'invitation par
user_idDiddiFreeID. POST /food/v1/restaurantscrée le dossier restaurant.GET /food/v1/restaurants/{restaurant_id}/documentslit les pièces et les motifs de refus.PUT /food/v1/restaurants/{restaurant_id}/documents/{document_type}dépose ou remplace une pièce refusée.- Les types MVP sont
manager_identityetbusiness_activity. POST /food/v1/restaurants/{restaurant_id}/submitsoumet le dossier.
États onboarding : draft, pending_review, approved, rejected. Après un
refus, afficher review_reason et rejection_reason, permettre le remplacement
des pièces refusées, puis une nouvelle soumission.
3.2 Profil, horaires et zone
Le manager peut modifier le profil, le logo, les horaires hebdomadaires, les
quartiers nommés et le service_radius_km. Les horaires sont interprétés en
Africa/Abidjan. Le frontend ne décide pas lui-même si un restaurant est
ouvert : il affiche is_open fourni par Food.
3.3 Menu Pro et disponibilité Cuisine
- Manager :
GET/POST /food/v1/restaurants/{restaurant_id}/dishes. - Modification :
PATCH /food/v1/restaurants/{restaurant_id}/dishes/{dish_id}. - Disponibilité :
PATCH /food/v1/restaurants/{restaurant_id}/dishes/{dish_id}/availability. - Cuisine :
GET /food/v1/restaurants/{restaurant_id}/kitchen/dishes.
Pour la création d'un plat, envoyer une Idempotency-Key stable pendant toute
la tentative. Même clé + même corps = même résultat ; même clé + corps
différent = 409 IDEMPOTENCY_CONFLICT.
La vue Cuisine reçoit DishAvailabilityOut sans prix mais avec image_url,
is_available et unavailable_until. Les prix ne doivent jamais être
affichés ni déduits côté rôle kitchen.
3.4 File de commandes et préparation
GET /food/v1/restaurants/{restaurant_id}/orders accepte le filtre répété
status, par exemple ?status=placed&status=preparing.
POST .../{order_id}/acceptavecprep_minutes: passe àpreparing.- Pour une commande online, l'API renvoie
409 PAYMENT_REQUIRED_BEFORE_PREPARATIONtant que DiddiPay n'est passucceeded. POST .../{order_id}/ready: passe àready.POST .../{order_id}/rejectavec un motif texte : passe àrejected.POST .../{order_id}/delivery/requestpermet de relancer une demande DiddiSend après une indisponibilité technique.
Le frontend Cuisine n'affiche pas les montants financiers. Le manager peut consulter la commande et la ventilation.
3.5 Finances restaurant
GET /food/v1/restaurants/{restaurant_id}/weekly-summaryaffiche les gains de la semaine en XOF et le fuseauAfrica/Abidjan.GET /food/v1/restaurants/{restaurant_id}/orders/{order_id}/financial-breakdownaffiche la ventilation connue de la commande.GET /food/v1/restaurants/{restaurant_id}/walletaffichepending_balance,available_balance,reserved_balance, le minimum de retrait et les frais.GET .../wallet/entriesliste les mouvements ; le filtreorder_idpermet de relier un gain à une commande.GET .../wallet/withdrawal-quotevérifie les frais et le montant net.POST .../wallet/withdrawalsdemande un retrait avec uneIdempotency-Keyet lequote_idretourné par le devis confirmé. Le backend refuse un devis expiré ou un montant différent.GET .../wallet/withdrawalsetGET .../wallet/withdrawals/{withdrawal_id}suivent le retrait.
Un gain cash reste pending jusqu'à l'événement DiddiSend
delivery.cash_remitted. Le frontend ne doit pas afficher ce montant comme
disponible avant cette régularisation. La recharge du wallet restaurant reste
hors MVP.
La disponibilité d'un plat est manuelle : envoyez is_available=true|false.
Le champ historique until_end_of_day est refusé et ne doit pas être utilisé;
Food ne réactive jamais automatiquement un plat.
4. Surfaces backend à ne pas appeler depuis le frontend public
Les routes suivantes sont réservées au Backoffice, Pilotage ou Support avec JWT S2S et scopes dédiés :
/food/v1/internal/backoffice/*/food/v1/internal/pilotage/*/internal/v1/support/order/{order_id}/context
Le frontend public ne doit pas stocker de client_secret, fabriquer un JWT
S2S ou appeler directement DiddiPay, DiddiSend ou DiddiFiles.
5. Gestion des erreurs côté interface
Lire error.code, error.message et error.request_id.
| Code | Affichage recommandé |
|---|---|
401 / UNAUTHORIZED |
Reconnecter via DiddiFreeID. |
403 / FORBIDDEN |
Compte sans rôle pour ce restaurant ou cette action. |
404 RATING_NOT_FOUND |
Aucune note encore enregistrée. |
409 PAYMENT_REQUIRED_BEFORE_PREPARATION |
Paiement en attente ; relire le paiement, puis réessayer l'acceptation. |
409 ORDER_INVALID_STATE |
Actualiser la commande avant de proposer une action. |
409 IDEMPOTENCY_CONFLICT |
Ne pas réutiliser la clé avec un autre corps. |
503 |
Dépendance indisponible ; garder l'action en attente et afficher le request_id. |
6. Ce qui est disponible, conditionnel ou hors MVP
| Surface | État actuel |
|---|---|
| Catalogue, recherche légère, pagination, rayon GPS | Exposé par l'API ; le rayon doit être activé/configuré avec le déploiement. |
| Commande livraison cash/online | MVP. |
| Paiement online avant préparation | MVP, garde activé par défaut. |
| Livraison et suivi | Projection Food de DiddiSend ; synchronisation dépend de la configuration du worker. |
| Remboursements | Demande client puis décision Support/Backoffice, exécution DiddiPay. |
| Onboarding R7 | Exposé : pièces, soumission, revue et remplacement. |
| Wallet et retrait restaurant | Exposé ; payout dépend de DiddiPay et cash de delivery.cash_remitted. |
| Pickup | Route présente mais porte désactivée par défaut. |
| Substitution | Route présente mais porte désactivée par défaut. |
| Recharge wallet restaurant | Hors MVP. |
| Surfaces Backoffice/Pilotage/Support | Backend S2S, jamais frontend public. |
7. Vérification frontend minimale avant recette
- Vérifier
/healthpuis/health/ready. - Vérifier le catalogue, une image
nullet une image résolue. - Créer une commande cash et vérifier
placed → preparing → ready. - Créer une commande online et vérifier que l'acceptation avant
succeededrenvoiePAYMENT_REQUIRED_BEFORE_PREPARATION. - Vérifier la préparation DiddiPay, le webhook et l'acceptation après
succeeded. - Vérifier le suivi DiddiSend et le rendu d'un échec projeté.
- Vérifier une demande de remboursement et ses statuts.
- Vérifier la notation existante et
RATING_NOT_FOUND. - Vérifier l'onboarding avec pièce refusée puis remplacement.
- Vérifier wallet
pending → availableaprèscash_remitted, puis le retrait idempotent.