Commerce Multi-Canal : L'Architecture d'un Checkout Unifié à Travers Cinq Fournisseurs
Comment construire un checkout unifié à travers plusieurs fournisseurs. Synchronisation bidirectionnelle, proxy de disponibilité en temps réel, scoping des canaux, routage des commandes et gestion des erreurs quand les fournisseurs tombent en plein checkout.
Multi-Canal, ce n'est pas "se connecter à Amazon"
La plupart des articles sur le commerce multi-canal décrivent comment connecter ton shop aux APIs de marketplaces. C'est de la syndication, pas de l'architecture. Le vrai commerce multi-canal, c'est : plusieurs fournisseurs avec des APIs différentes, des modèles de prix différents, des formats de disponibilité différents et des flux de réservation différents, le tout présenté au client comme un seul checkout fluide.
On a construit une plateforme d'agrégation multi-fournisseurs qui unifie le checkout à travers 5 APIs de fournisseurs. Un panier, cinq fournisseurs, une commande. Cet article couvre l'architecture qui rend tout ça possible. Pour l'évaluation plus large des plateformes commerce, consulte notre guide Vendure en production et notre guide des plateformes e-commerce.
Le Problème du Checkout Unifié
Chaque fournisseur a sa propre API, son propre format de données, son propre modèle de disponibilité et son propre flux de réservation :
| Aspect | Fournisseur A | Fournisseur B | Fournisseur C |
|---|---|---|---|
| Style d'API | REST v2 | GraphQL | SOAP/XML |
| Tarification | Par personne, dynamique | Prix fixe, paliers | Par groupe, négocié |
| Disponibilité | API temps réel | Fichier de sync quotidien | Webhook au changement |
| Réservation | Deux étapes (réserver + confirmer) | Une étape (réservation instantanée) | Trois étapes (devis + réserver + confirmer) |
| Annulation | Gratuite jusqu'à 24h | Non remboursable | Remboursement partiel |
| Format d'ID | UUID | Numérique | Préfixe alphanumérique |
Un checkout unifié doit normaliser tout ça derrière une seule interface. Le client ajoute des articles de différents fournisseurs dans un seul panier et paie une seule fois. Le système route chaque article vers le bon fournisseur, gère le flux de réservation par fournisseur et présente une seule confirmation au client.
Le Pattern Supplier Adapter
Chaque fournisseur reçoit un adapter qui implémente une interface commune :
interface SupplierAdapter {
search(query: SearchQuery): Promise<Product[]>;
checkAvailability(productId: string, date: string, persons: PersonConfig[]): Promise<AvailabilityResult>;
reserve(params: ReservationParams): Promise<Reservation>;
confirm(reservationId: string, bookerInfo: BookerInfo): Promise<BookingConfirmation>;
cancel(bookingId: string): Promise<CancellationResult>;
}
// Chaque fournisseur implémente l'interface différemment
class SupplierAAdapter implements SupplierAdapter {
async checkAvailability(productId: string, date: string, persons: PersonConfig[]) {
// Fournisseur A : appel API temps réel
const response = await this.httpClient.get(`/v2/availability/${productId}`, {
params: { date, adults: persons.filter(p => p.type === 'adult').length },
});
return this.normalizeAvailability(response.data);
}
}
class SupplierBAdapter implements SupplierAdapter {
async checkAvailability(productId: string, date: string, persons: PersonConfig[]) {
// Fournisseur B : requête GraphQL
const { data } = await this.graphqlClient.query({
query: AVAILABILITY_QUERY,
variables: { productId, date },
});
return this.normalizeAvailability(data.availability);
}
}
L'adapter normalise la réponse de chaque fournisseur dans un format commun. Le service de checkout travaille avec le format commun, jamais avec les structures de données spécifiques aux fournisseurs.
Proxy de Disponibilité en Temps Réel
Certains fournisseurs proposent des prix en temps réel qui changent à la minute (tarification dynamique, inventaire limité). Les résultats de recherche montrent un prix en cache, mais au moment du checkout le système doit vérifier le prix actuel.
async function getAvailabilityWithFallback(
productId: string, date: string, persons: PersonConfig[]
): Promise<AvailabilityResult> {
const supplier = getSupplierForProduct(productId);
if (supplier.supportsRealTimeAvailability) {
try {
// Prix live depuis l'API du fournisseur
const live = await supplier.adapter.checkAvailability(productId, date, persons);
await cache.set(`avail:${productId}:${date}`, live, { ttl: 300 });
return live;
} catch (error) {
// API du fournisseur en panne : retourner le prix en cache avec avertissement
const cached = await cache.get(`avail:${productId}:${date}`);
if (cached) {
return { ...cached, stale: true, warning: 'Price may have changed' };
}
throw new AvailabilityError('Cannot determine availability');
}
}
// Fournisseur avec disponibilité en batch : retourner depuis l'index
return searchIndex.getAvailability(productId, date);
}
Le pattern proxy : essayer en live, se rabattre sur le cache, se rabattre sur l'index. Toujours informer le client si le prix est potentiellement obsolète.
Synchronisation Bidirectionnelle : Empêcher les Boucles Infinies
Quand deux systèmes synchronisent des données dans les deux sens (système commerce et système opérations), chaque mise à jour du système A déclenche un sync vers le système B, qui déclenche un sync retour vers A.
// Le suivi de source empêche les boucles infinies
interface SyncMessage {
entityId: string;
entityType: string;
data: any;
source: string; // "commerce" | "operations" | "import"
correlationId: string;
}
async function handleSync(message: SyncMessage) {
// Ignorer les messages qui viennent de ce système
if (message.source === THIS_SYSTEM_ID) {
return; // ACK et passer
}
// Traiter la synchronisation
await updateEntity(message.entityId, message.data);
// Publier la mise à jour avec NOTRE tag source
await publishSync({
...message,
source: THIS_SYSTEM_ID, // Taguer pour que l'autre système l'ignore
});
}
Chaque message porte un champ source. Quand un système reçoit un message de lui-même (via l'autre système), il l'ignore. La boucle est cassée dès le premier aller-retour.
Pour d'autres patterns event-driven incluant la déduplication et le dead letter handling, consulte notre guide d'architecture event-driven.
Channel Scoping : Visibilité vs Publication vs Disponibilité
Trois concepts qui doivent rester séparés :
| Concept | Question | Exemple |
|---|---|---|
| Visibilité | Quels fournisseurs ce canal peut-il voir ? | Le site allemand voit les fournisseurs A, B, C. L'API partenaire ne voit que le fournisseur A. |
| Publication | Ce produit est-il publié et actif ? | Le produit existe mais n'est pas publié (brouillon). |
| Disponibilité | Ce produit peut-il être réservé maintenant ? | Le produit est publié mais épuisé pour samedi. |
// Le canal détermine la visibilité
const channel = await channelStore.get(tenantId, channelId);
const visibleSupplierIds = channel.supplierIds;
// Filtrer les produits par visibilité du canal
const products = await searchIndex.search({
query: userQuery,
filters: {
supplier_id: { $in: visibleSupplierIds }, // Scoping du canal
status: 'active', // Publication
},
});
// La disponibilité est vérifiée au moment du checkout (concern séparé)
Un produit peut être visible (dans la liste fournisseurs du canal), publié (statut = actif), mais indisponible (épuisé). Chacun est un filtre différent à un stade différent du flux.
Routage des Commandes
Quand une commande contient des articles de plusieurs fournisseurs, chaque article doit être routé vers le bon fournisseur pour le fulfillment :
async function processMultiSupplierOrder(order: Order): Promise<OrderResult> {
// Grouper les articles par fournisseur
const itemsBySupplier = groupBy(order.items, item => item.supplierId);
// Traiter les articles de chaque fournisseur indépendamment
const results = await Promise.allSettled(
Object.entries(itemsBySupplier).map(async ([supplierId, items]) => {
const adapter = getAdapter(supplierId);
// Réserver
const reservation = await adapter.reserve({
items,
booker: order.bookerInfo,
expiresIn: 900, // 15 min de maintien
});
// Confirmer
return adapter.confirm(reservation.id, order.bookerInfo);
})
);
// Gérer les résultats mixtes
const successful = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
if (failed.length > 0 && successful.length > 0) {
// Succès partiel : certains fournisseurs réservés, d'autres échoués
// Annuler les réservations réussies ? Ou confirmer la commande partielle ?
// C'est une décision business, pas technique.
return handlePartialSuccess(order, successful, failed);
}
return { status: failed.length === 0 ? 'COMPLETED' : 'FAILED', results };
}
Le Problème du Succès Partiel
Que se passe-t-il quand le fournisseur A confirme mais le fournisseur B échoue ? Les options :
| Stratégie | Comportement | Idéal pour |
|---|---|---|
| Tout ou rien | Annuler le fournisseur A si B échoue | Commandes haute valeur, événements |
| Fulfillment partiel | Confirmer A, notifier le client pour B | Produits interchangeables |
| Retry puis annuler | Retenter B 3 fois, annuler A si B échoue définitivement | Équilibre UX et fiabilité |
La bonne stratégie dépend du business. Pour des billets d'événements (non fongibles), tout ou rien est généralement correct. Pour des produits interchangeables, le fulfillment partiel est meilleur.
Gestion des Erreurs : Quand les Fournisseurs Tombent en Plein Checkout
| Point de défaillance | Ce qui s'est passé | Réponse |
|---|---|---|
| Pendant la recherche | Timeout de l'API fournisseur | Afficher les résultats des autres fournisseurs |
| Pendant la vérification de disponibilité | Prix obsolète | Afficher le prix en cache avec avertissement |
| Pendant la réservation | Stock épuisé | Retirer l'article du panier, suggérer des alternatives |
| Pendant la confirmation | Paiement refusé par le fournisseur | Annuler la réservation, retenter le paiement ou rembourser |
| Après la confirmation | Le fournisseur envoie une annulation | Notifier le client, proposer une nouvelle réservation ou un remboursement |
| Pendant l'annulation | API du fournisseur en panne | Mettre l'annulation en file d'attente pour retry |
Chaque erreur doit être gérée explicitement. "Une erreur est survenue" n'est jamais un message d'erreur acceptable pour une transaction commerce.
Pièges Courants
-
Supposer que tous les fournisseurs ont le même flux de réservation. Les flux en deux étapes, une étape et trois étapes existent tous. L'adapter doit les normaliser.
-
Ignorer les changements de prix entre la recherche et le checkout. La tarification dynamique signifie que le prix au checkout peut différer du prix de recherche. Valider au moment du checkout.
-
Pas de suivi de source sur la synchronisation bidirectionnelle. Sans ça, chaque mise à jour boucle infiniment entre les systèmes.
-
Traiter la visibilité des canaux comme un concern UI. Le scoping des canaux doit être appliqué au niveau de la couche query, pas juste côté frontend. Les consommateurs d'API voient les mêmes filtres.
-
Pas de stratégie de défaillance partielle. Les commandes multi-fournisseurs vont partiellement échouer. Définis la politique business avant que ça arrive en production.
-
Appels synchrones aux fournisseurs pendant le checkout. Si un fournisseur est lent, tout le checkout attend. Traite les fournisseurs en parallèle avec des timeouts individuels.
Points Clés
-
Les supplier adapters normalisent la diversité. Chaque fournisseur reçoit un adapter implémentant une interface commune. La logique business ne touche jamais les formats spécifiques aux fournisseurs.
-
La disponibilité en temps réel est un pattern proxy. Essayer en live, se rabattre sur le cache, se rabattre sur l'index. Toujours communiquer la fraîcheur des données au client.
-
Le suivi de source empêche les boucles de sync. Chaque message porte son origine. Les systèmes ignorent les messages venant d'eux-mêmes.
-
Visibilité, publication et disponibilité sont trois concerns séparés. Le canal détermine la visibilité. Le statut du produit détermine la publication. L'inventaire détermine la disponibilité. Ne les confonds pas.
-
La défaillance partielle est une décision business. Tout ou rien vs fulfillment partiel dépend du type de produit. Définis la politique avant de construire le code.
On construit des systèmes de commerce multi-canal dans le cadre de notre pratique e-commerce et logiciel sur mesure. Si tu as besoin d'aide avec l'architecture d'intégration fournisseurs, parle à notre équipe ou demande un devis.
Sujets couverts
Guides connexes
Architecture Event-Driven en pratique : ce qui tourne vraiment mal
Patterns réels d'architecture event-driven en production. Event storms, boucles de sync bidirectionnelle, dead letters, stores d'idempotence, et choix entre Kafka, RabbitMQ, BullMQ et Symfony Messenger.
Lire le guideGuide Entreprise des Systèmes d'IA Agentiques
Guide technique des systemes d'IA agentiques en entreprise. Decouvre l'architecture, les capacites et les applications des agents IA autonomes.
Lire le guideCommerce Agentique : Comment laisser les agents IA acheter en toute securite
Comment concevoir un commerce agentique gouverne. Moteurs de politiques, portes d'approbation HITL, reçus HMAC, idempotence, isolation multi-tenant et le protocole Agentic Checkout complet.
Lire le guidePrêt à construire des systèmes IA prêts pour la production ?
Notre équipe est spécialisée dans les systèmes IA prêts pour la production. Discutons de comment nous pouvons aider.
Démarrer une conversation