hooks/useCheckout.ts

hook·mobile·3.7 KB · 113 lignes· Voir l'itinéraire

Annotation

Ce fichier contient des hooks React pour gérer le processus de commande mobile. Il expose `usePaiementVitrine` pour récupérer les modes de paiement disponibles, et `useCreerCommande` pour soumettre une nouvelle commande. `useCreerCommande` gère également le mode hors-ligne en mettant en file d'attente les commandes non envoyées et expose `isCommandeQueued` pour vérifier si une commande a été mise en attente. Ces hooks sont utilisés dans l'interface utilisateur du processus de paiement et de création de commande.

7 exports

usePaiementVitrineisCommandeQueueduseCreerCommandeCreerCommandePayloadCommandeCreatedCOMMANDE_OFFLINE_QUEUEDCommandeResult

Code source· typescript

import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { apiGet, apiPost } from '@/lib/api';
import { API_MOBILE } from '@/lib/constants';
import type {
  PaiementVitrineResponse,
  ModePaiementCommande,
} from '@/types/api';

/**
 * GET /mobile/[slug]/paiement (public).
 * Retourne `modesAcceptes[]` avec codeUSSD/lienWave templates contenant
 * `{MONTANT}` placeholder à remplacer côté client avec le total panier.
 */
export function usePaiementVitrine(slug: string | null) {
  return useQuery({
    queryKey: ['paiement-vitrine', slug],
    queryFn: async () => {
      const { data, error } = await apiGet<PaiementVitrineResponse>(
        API_MOBILE.PAIEMENT_VITRINE(slug!),
      );
      if (error || !data) throw new Error(error ?? 'Erreur paiement');
      return data;
    },
    enabled: !!slug,
    staleTime: 5 * 60 * 1000, // 5 min — config tenant change rarement
  });
}

export type CreerCommandePayload = {
  tenantId: string;
  lignes: {
    produitId: string;
    varianteId: string | null;
    quantite: number;
    prix: number;
  }[];
  modePaiement: ModePaiementCommande;
  refPaiement?: string | null;
  noteClient?: string | null;
  instructionsPaiement?: string | null;
};

export type CommandeCreated = {
  id: string;
  tenantId: string;
  total: number;
  devise: string;
  statut: string;
  statutPaiement: string;
  modePaiement: ModePaiementCommande;
  refPaiement: string | null;
  instructionsPaiement: string | null;
  createdAt: string;
};

/**
 * Sentinel renvoyé par useCreerCommande quand la commande a été placée dans
 * la queue offline (réseau down). L'appelant doit afficher un toast
 * "Commande en attente — envoyée dès reconnexion" plutôt qu'une erreur.
 */
export const COMMANDE_OFFLINE_QUEUED = '__OFFLINE_QUEUED__' as const;

export type CommandeResult =
  | CommandeCreated
  | { __offline: true; queuedAt: number };

export function isCommandeQueued(r: CommandeResult): r is { __offline: true; queuedAt: number } {
  return (r as { __offline?: boolean }).__offline === true;
}

/**
 * POST /mobile/commandes (Bearer CLIENT requis).
 *
 * Crée la Commande + lignes en transaction côté backend, décremente les stocks,
 * trigger push gérant + email best-effort. onSuccess invalide les queries
 * commandes (l'écran Activité refetch).
 *
 * Mode hors-ligne : si le réseau est down au moment du tap, on enqueue la
 * commande dans `offlineQueueStore` et on retourne `{ __offline: true }` —
 * la commande sera rejouée à la reconnexion. Perdre une commande = perdre
 * un client, donc on garantit que rien n'est perdu côté UX.
 */
export function useCreerCommande() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (payload: CreerCommandePayload): Promise<CommandeResult> => {
      const { data, error } = await apiPost<{ commande: CommandeCreated }>(
        API_MOBILE.COMMANDES,
        payload,
        {
          offlineQueue: {
            type: 'COMMANDE',
            label: `Commande ${payload.tenantId.slice(0, 8)} — ${payload.lignes.length} ligne(s)`,
          },
        },
      );
      if (error === 'OFFLINE_QUEUED') {
        return { __offline: true as const, queuedAt: Date.now() };
      }
      if (error || !data) throw new Error(error ?? 'Erreur création commande');
      return data.commande;
    },
    onSuccess: (result) => {
      // Cas online normal : invalidate. Cas offline : pas besoin (rien créé
      // côté serveur, l'invalidate se fera quand la queue flush + push reçu).
      if (!isCommandeQueued(result)) {
        qc.invalidateQueries({ queryKey: ['mes-commandes'] });
        qc.invalidateQueries({ queryKey: ['catalogue'] });
      }
    },
  });
}