hooks/useCheckout.ts
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'] });
}
},
});
}
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'] });
}
},
});
}