Ce brief décrit le contrat frontend DiddiFood vérifié le 22 octobre 2026. Il s'adresse aux développeurs client, Restaurant Pro, QA et intégrateurs DiddiFreeID.

Brief frontend DiddiFood — v2026.10.22

1. Résultat de la vérification

  • Contrat local après le lot client 2 : docs/api/openapi.json, 70 routes.
  • Staging vérifié : https://diddifood-backend-staging.diddifree.com.
  • /health, /docs, /openapi.json et /metrics répondent HTTP 200.
  • L'OpenAPI staging contient 66 routes. Il manque actuellement :
  • POST /food/v1/internal/backoffice/refund-requests/{refund_request_id}/approve ;
  • POST /food/v1/internal/backoffice/refund-requests/{refund_request_id}/reject ;
  • GET /internal/v1/support/order/{order_id}/context.
  • Le contrat local ajoute POST /food/v1/restaurants/{restaurant_id}/quote. Il calcule le sous-total, le frais Food configuré et le total XOF avant création de commande.
  • Le devis utilise encore DIDDIFOOD_DELIVERY_FEE_XOF. Il ne remplace pas encore un devis dynamique DiddiSend.
  • Les statuts de livraison sont maintenant documentés dans OpenAPI : requested, assigned, picked_up, in_transit, delivered et failed.
  • La liste publique des restaurants répond 200. Le restaurant QA contrôlé avait image_url: null et un menu vide. Le frontend doit donc prévoir un fallback image et un état catalogue vide.

Le contrat local est la référence de développement. Pour une recette staging, le frontend doit utiliser uniquement les routes réellement présentes dans l'OpenAPI staging jusqu'au prochain déploiement.

2. Adresse et authentification

API staging: https://diddifood-backend-staging.diddifree.com
Identité staging: https://auth-staging.diddifree.com

Les routes privées utilisent :

Authorization: Bearer <JWT DiddiFreeID>

Le frontend ne déduit jamais le rôle Pro d'un rôle global du JWT. Il appelle GET /food/v1/me/restaurants, puis utilise le role et le restaurant.id renvoyés par Food. Les rôles manager et kitchen appartiennent à DiddiFood.

3. Conventions d'affichage

  • Devise : XOF (franc CFA). Les montants sont des entiers *_minor_units; les afficher comme des francs XOF sans inventer de décimales.
  • Les dates sont ISO 8601, souvent en UTC (Z). Les afficher dans le fuseau Africa/Abidjan.
  • image_url et logo_file_id/photo_file_id sont optionnels. Utiliser image_url quand elle est renseignée; ne jamais construire une URL à partir d'un file_id.
  • is_open, rating, review_count, delivery_time_minutes et delivery_fee_minor_units peuvent être null.

4. Parcours client ordinaire

Catalogue

  1. GET /food/v1/restaurants?limit=20&offset=0.
  2. GET /food/v1/restaurants/{restaurant_id} pour le détail.
  3. GET /food/v1/restaurants/{restaurant_id}/menu pour les plats disponibles.
  4. Afficher price_minor_units, currency, is_available, image_url, l'ouverture et le délai estimé.

Devis avant commande

Appeler POST /food/v1/restaurants/{restaurant_id}/quote avec items, pickup_mode et les coordonnées de livraison. Afficher subtotal_minor_units, delivery_fee_minor_units et total_minor_units renvoyés par Food. Ne pas recalculer ces montants dans le frontend.

Création de commande

Pour le MVP, envoyer pickup_mode=delivery et l'un des deux moyens de paiement acceptés :

{
  "items": [{"dish_id": "<uuid>", "quantity": 2}],
  "payment_method": "cash_on_delivery",
  "pickup_mode": "delivery",
  "dropoff_address": "Cocody, Abidjan",
  "dropoff_latitude": 5.3611,
  "dropoff_longitude": -4.0079,
  "recipient_name": "Nom du destinataire",
  "recipient_phone": "+2250700000000"
}

POST /food/v1/orders exige au moins un article. Pour la livraison, l'adresse, les coordonnées et les contacts du destinataire sont nécessaires. payment_method accepte exactement cash_on_delivery ou online.

Paiement

  • Cash : afficher l'état de paiement depuis GET /food/v1/orders/{order_id}/payment; ne pas considérer la commande ready comme un paiement confirmé.
  • En ligne : appeler POST /food/v1/orders/{order_id}/payments/prepare avec { "customer_email": "..." }. Ouvrir next_action_url si elle est fournie, puis appeler GET /payment ou POST /payment/refresh pour la confirmation.
  • Le client choisit « paiement en ligne »; il ne choisit pas Orange Money, Wave ou un autre réseau dans DiddiFood. DiddiPay gère ce choix.
  • Les statuts publiés sont pending, requires_action, succeeded, failed, cancelled, expired, collected_cash, refunded et partially_refunded.
  • Le retour navigateur /food/v1/payments/return/{token} ne confirme jamais le paiement. Après retour, relire GET /food/v1/payment-returns/{token} puis GET /food/v1/orders/{order_id}/payment.

Suivi, annulation et notation

  • GET /food/v1/orders et GET /food/v1/orders/{order_id} sont les lectures de référence.
  • POST /food/v1/orders/{order_id}/cancel est disponible seulement avant l'acceptation du restaurant.
  • La commande suit placed → preparing → ready → picked_up → delivered ou devient rejected/cancelled.
  • Après delivered, POST /food/v1/orders/{order_id}/rating accepte stars de 1 à 5 et un commentaire optionnel.
  • Pour un incident, relire GET /food/v1/orders/{order_id}/delivery. Un statut failed doit afficher une action Support ou remboursement ; DiddiSend reste la source de vérité du coursier, de la position et de la preuve de remise.

Litige et remboursement

  • Créer une demande avec POST /food/v1/orders/{order_id}/refund-requests.
  • Suivre les demandes avec GET /food/v1/orders/{order_id}/refund-requests.
  • RefundRequestOut.status peut être submitted, approved, rejected, processing, refunded ou failed.
  • Le client envoie uniquement un motif texte libre. Food calcule le montant comme le total du paiement enregistré pour la commande ; le client ne choisit pas le montant et les remboursements partiels ne sont pas proposés.
  • L'approbation ou le rejet passent par le circuit Backoffice audité avec food:payments:write. Les agents Support autorisés et les administrateurs métier Food peuvent décider ; DiddiPay exécute ensuite le mouvement.

5. Temps réel et notifications

Connexion optionnelle :

wss://diddifood-backend-staging.diddifree.com/food/v1/ws?token=<JWT>

Le WebSocket envoie une enveloppe {event,title,body,data}. Il n'y a pas de replay : après reconnexion, relire la commande, le paiement et la livraison par HTTP. Le push FCM est best-effort et peut être absent.

Enregistrer le token avec POST /food/v1/devices/register (fcm_token, platform) après authentification, puis le retirer avec /food/v1/devices/unregister au changement de compte. Les événements Food principaux sont order_confirmed, order_ready, order_delivered et order_cancelled. Le push ne remplace jamais la lecture HTTP.

6. Parcours Restaurant Pro

  • GET /food/v1/me/restaurants : restaurant(s) liés à l'utilisateur et rôle.
  • Manager : profil, horaires, zones, logo, plats, commandes et wallet.
  • Kitchen : file de préparation et disponibilité des plats sans prix.
  • Acceptation : POST /food/v1/restaurants/{restaurant_id}/orders/{order_id}/accept avec { "prep_minutes": 15 } (1 à 180).
  • Prêt : POST /food/v1/restaurants/{restaurant_id}/orders/{order_id}/ready.
  • Les livraisons, livreurs, collecte et remise cash restent la propriété de DiddiSend.

Images de plat : photo_file_id à l'écriture, image_url à la lecture si DiddiFiles est configuré. Logo restaurant : même principe avec logo_file_id et image_url.

7. Wallet restaurant

Le frontend Pro peut lire :

  • GET /food/v1/restaurants/{restaurant_id}/wallet ;
  • GET /food/v1/restaurants/{restaurant_id}/wallet/entries ;
  • GET /food/v1/restaurants/{restaurant_id}/wallet/withdrawal-quote ;
  • POST /food/v1/restaurants/{restaurant_id}/wallet/withdrawals ;
  • GET /food/v1/restaurants/{restaurant_id}/wallet/withdrawals/{withdrawal_id}.

Afficher séparément pending_balance, available_balance et reserved_balance. Le minimum de retrait staging est actuellement 5 000 XOF; les frais renvoyés par l'API restent la source d'affichage. La recharge restaurant est hors MVP.

8. Erreurs à traiter

HTTP Code ou situation UX attendue
401 JWT absent/expiré Reconnecter avec DiddiFreeID.
403 Rôle ou restaurant interdit Masquer l'action et afficher un accès refusé.
404 Ressource absente Recharger la liste ou afficher « introuvable ».
409 Transition interdite, pickup désactivé, paiement déjà traité Relire l'état avant de proposer une action.
422 Corps invalide Afficher les champs à corriger.
503 DiddiPay, DiddiSend ou DiddiFiles indisponible Conserver l'état local, proposer de réessayer et ne pas inventer un succès.

9. Fonctionnalités à ne pas afficher en MVP

  • pickup_mode=pickup : le backend répond PICKUP_DISABLED tant que DIDDIFOOD_PICKUP_ENABLED=false.
  • Substitution de plat : porte DIDDIFOOD_SUBSTITUTION_ENABLED=false.
  • Recharge du wallet restaurant : hors MVP.
  • Routes Backoffice de remboursement et Support absentes du staging actuel : ne pas les appeler avant déploiement.

10. Scénarios QA frontend

  1. Catalogue vide et image_url=null.
  2. Catalogue avec image et fallback si URL absente.
  3. Commande cash en livraison, suivi placed jusqu'à delivered.
  4. Commande online, redirection next_action_url, refresh et confirmation.
  5. Annulation avant acceptation puis refus après acceptation.
  6. Manager avec deux restaurants et kitchen sans accès aux prix.
  7. Wallet avec soldes séparés et retrait inférieur au minimum refusé.
  8. Reconnexion WebSocket suivie d'une relecture HTTP.

Contrat complet : docs/api/openapi.json. Parcours backend : customer-order-flow.md, restaurant-pro-flow.md, payments-wallet.md et delivery-flow.md.