Table des matières :
- Ce que le cœur PrestaShop couvre (et ce qu’il laisse à votre charge) en B2B
- Groupes clients : modèle, règles d’accès, et pièges multi‑boutique
- Tarifs B2B : SpecificPrice, priorités, paliers quantité et conflits de règles
- Devis : mettre en place un workflow robuste (sans tordre Order/Cart)
- Commandes en gros : quick order, import, et contraintes de performance
- Intégration ERP et gouvernance des tarifs : source de vérité, sync et files
- Industrialiser : tests, CI/CD, sécurité et déploiement sans casse B2B
PrestaShop B2B, en pratique, c’est surtout une combinaison de groupes clients, de règles de prix (SpecificPrice + remises) et d’un workflow de devis/commande en gros que le cœur ne fournit pas entièrement. Les exemples ci-dessous sont contextualisés pour PrestaShop 8.1.x et 9.0/9.1 (mêmes concepts), avec PHP 8.2/8.3. Avant toute modification de la tarification ou du schéma, partez du principe que vous devrez versionner la config, tester sur staging et sauvegarder la base (dump + restauration testée).
Dans un contexte FR/EU, le B2B implique souvent aussi : TVA intracommunautaire, SIRET/identifiants légaux, franco de port, conditions de paiement (virement, échéance), et une attente forte côté acheteurs : retrouver leurs prix contractuels à l’identique, du devis à la facture. L’objectif de cet article est de vous aider à éviter les deux écueils classiques : (1) un modèle de prix “empilé” impossible à maintenir, (2) un devis qui ne “rejoue” jamais pareil.
Ce que le cœur PrestaShop couvre (et ce qu’il laisse à votre charge) en B2B
Le cœur de PrestaShop sait gérer du B2B « simple » : champs société (mode B2B), groupes, prix spécifiques, minimum de commande par produit, règles paniers. Ça suffit pour des cas basiques (revendeurs avec -10% global, MOQ par SKU, TVA intracom avec validation). En revanche, dès que vous touchez au pricing contractuel (par client + par SKU + par palier), au devis et aux commandes en gros rapides, vous sortez vite des rails.
La bascule Mode B2B (Back‑Office → Paramètres de la boutique) ajoute des champs (SIRET/APE, site web, etc.) et des comportements (ex. affichage de la TVA selon configuration). C’est utile mais ce n’est pas un moteur « ERP-lite ». La validation de TVA intracom repose sur votre implémentation/processus (ou module), et l’UI du BO n’est pas optimisée pour maintenir des milliers de lignes de tarifs négociés.
Pour cadrer rapidement, voici ce que PrestaShop gère “nativement” sans custom lourd, vs. ce que vous devrez généralement concevoir :
- OK out-of-the-box (si vous gardez un B2B simple) :
- segmentation par groupes,
- prix spécifiques (y compris paliers via
from_quantity), - restrictions d’accès catégories/produits,
- affichage/masquage des prix (selon configuration),
- taxes via règles de taxes (attention aux cas intracom et aux exemptions).
- À concevoir (souvent via module + process) :
- workflow de devis (expiration, acceptation, relances, PDF propre),
- conditions de paiement et limites de crédit (ex. paiement à 30/45/60 jours),
- gestion d’un numéro de bon de commande (PO number) obligatoire chez certains acheteurs,
- gouvernance des prix contractuels (source de vérité, purge, audit, rollback),
- quick order performant (import, copier-coller de références, panier à 100+ lignes).
Sur la partie devis, il n’y a pas de concept natif « devis » comparable à un panier figé, validable, avec date d’expiration et conversion en commande. Vous pouvez bricoler avec des états de commande, mais si vous ne snapshottez pas les prix et conditions, vous perdez la reproductibilité. Une citation souvent attribuée à Phil Karlton illustre bien le problème (attribution discutée mais formule largement reprise) : « There are only two hard things in Computer Science: cache invalidation and naming things. » — remplacez “cache invalidation” par “pricing invalidation” et vous avez le problème B2B : quand et comment un prix cesse d’être vrai ?
Groupes clients : modèle, règles d’accès, et pièges multi‑boutique
Dans PrestaShop, un groupe sert à deux choses : (1) catégoriser les clients (revendeur, grossiste, VIP, etc.), (2) piloter des conditions commerciales (réduction de groupe, affichage des prix, accès aux catégories). Techniquement, le mapping est porté par ps_group, ps_customer_group et, en multi‑boutique, par les tables associées au contexte shop (selon versions / tables _shop). Le groupe par défaut d’un client est crucial : c’est lui qui sera utilisé si plusieurs groupes sont assignés.
Le piège classique côté FO : vous croyez “avoir mis le client dans le groupe Grossiste”, mais le groupe par défaut reste “Client” et vos calculs de prix tombent sur la mauvaise priorité. Dans votre code, gardez en tête : Context::getContext()->customer->id_default_group n’est pas forcément “le premier groupe” renvoyé par getGroups(). Faites des audits en SQL (sur une copie) :
SELECT c.id_customer, c.email, c.id_default_group, GROUP_CONCAT(cg.id_group) AS groups
FROM ps_customer c
LEFT JOIN ps_customer_group cg ON cg.id_customer = c.id_customer
GROUP BY c.id_customer
HAVING FIND_IN_SET(c.id_default_group, groups) = 0;
Ajoutez aussi un contrôle métier simple, qui évite des semaines de support : un client B2B doit‑il pouvoir voir des prix HT, TTC, ou aucun prix tant qu’il n’est pas approuvé ? Dans beaucoup de catalogues B2B, le flux ressemble à :
- inscription avec champs société,
- statut “à valider” (compte désactivé ou groupe “Pros à valider”),
- bascule manuelle/automatique vers le groupe final (“Revendeur France”, “Revendeur UE”, “Grand compte”).
En multi‑boutique, évitez de laisser des groupes “globaux” sans réflexion : un même client peut exister dans plusieurs shops, mais pas forcément avec les mêmes conditions (catalogue, taxes, devises). Et selon votre configuration multi‑boutique, vous pouvez aussi partager (ou non) les clients entre boutiques : ce choix impacte directement la cohérence des groupes et l’effort de maintenance.
Mini-scenario typique (FR/EU) qui révèle vite les failles de modélisation :
- Shop A (France) : prix en EUR, TVA FR, franco à 500€ HT, groupe “Revendeurs FR”.
- Shop B (UE) : prix en EUR, autoliquidation TVA possible si TVA intracom valide, conditions de livraison différentes, groupe “Revendeurs UE”.
- Un même compte “Acheteur” doit parfois exister sur les deux, mais avec règles de taxes et parfois prix distincts (contrat local vs export).
Sur PrestaShop 9, le contexte Symfony est plus présent côté BO, mais la logique de groupes en FO reste largement héritée. Si vous êtes en phase de migration, verrouillez votre cible (ex. 9.0 + PHP 8.3) et rétestez les règles de groupes après migration (voir la logique de montée de version dans PrestaShop 9 : guide de migration Symfony 6.4 et PHP 8.3).
Tarifs B2B : SpecificPrice, priorités, paliers quantité et conflits de règles
Le moteur de prix B2B de PrestaShop, c’est essentiellement ps_specific_price + les remises de groupe/catégorie + les règles panier (cart rules). Un Specific Price peut être ciblé par : boutique, devise, pays, groupe, client, produit, déclinaison, quantité minimale, période. C’est extrêmement flexible… et donc très facile à rendre incohérent si vous empilez des règles sans gouvernance.
La difficulté, ce n’est pas “créer un prix spécifique”, c’est de contrôler la priorité quand plusieurs lignes matchent. PrestaShop maintient un champ de priorité (id_specific_price_rule / logique interne + ordre des critères). Pour auditer les collisions, commencez par repérer les produits ayant plusieurs SpecificPrice pour un même triplet (produit, groupe/client, quantité) :
SELECT id_product, id_group, id_customer, from_quantity, COUNT(*) AS n
FROM ps_specific_price
WHERE id_shop IN (0, 1) -- adaptez
GROUP BY id_product, id_group, id_customer, from_quantity
HAVING n > 1
ORDER BY n DESC;
Pour éviter d’“inventer” des règles au fil de l’eau, posez une convention lisible (et audit-able) dès le départ. Exemple de gouvernance simple, adaptée à beaucoup de B2B :
- Niveau 1 (contrat client) : prix spécifiques
id_customer(prioritaires, rares, justifiés). - Niveau 2 (grille par groupe) : prix spécifiques
id_group+ paliers quantité (majoritaires). - Niveau 3 (promo temporaire) : règles panier, uniquement si vous acceptez qu’un devis doive les figer.
- Interdiction (ou forte limitation) des “règles surprises” : multiples remises cumulées non documentées.
Pour les commandes en gros, les paliers sont souvent indispensables (ex. 1–9 : prix A, 10–49 : prix B, 50+ : prix C). PrestaShop sait faire avec from_quantity, mais l’ergonomie de maintenance est mauvaise dès que vous dépassez quelques centaines de lignes. Deux approches réalistes :
- Pricing “catalogue” : injecter des SpecificPrice en masse (import/ETL) et laisser PrestaShop calculer localement (rapide à l’affichage, lourd à maintenir).
- Pricing “contractuel externe” : calculer le prix via ERP/CRM et ne pousser que des snapshots au moment du devis/commande (maintenance plus simple, mais attention à la latence et au cache).
Sur la volumétrie : une table ps_specific_price qui grossit sans contrôle devient vite un problème de performance et de cohérence. Deux pratiques simples qui évitent beaucoup de douleur :
- Purger régulièrement les lignes expirées (si vous utilisez
from/to) et archiver ailleurs si besoin (sur un export). - Indexer intelligemment (à valider sur votre version et votre charge) : les filtres classiques portent sur
id_product,id_shop,id_currency,id_country,id_group,id_customer,from_quantityet les dates.
Exemple de purge (à exécuter uniquement après validation sur staging + backup) :
DELETE FROM ps_specific_price
WHERE `to` <> '0000-00-00 00:00:00'
AND `to` < NOW();
Dans tous les cas, ne modifiez pas le calcul au hasard : utilisez les hooks dédiés (selon versions) comme actionProductPriceCalculation/actionGetProductPropertiesAfter si vous devez surcharger le prix. Et gardez en tête le rappel de Donald E. Knuth (formulé notamment dans son texte de 1974 sur le “structured programming”) : « Premature optimization is the root of all evil. » (Knuth, 1974). Stabilisez d’abord votre modèle de règles et votre stratégie de priorité, sinon vous risquez d’optimiser un système… incorrect.
Devis : mettre en place un workflow robuste (sans tordre Order/Cart)
Le devis en B2B a trois exigences techniques : (1) figer les prix, (2) figer les quantités/produits, (3) permettre la conversion en commande sans recalcul destructeur. Si vous vous contentez de “sauver un panier” et de laisser le client revenir une semaine plus tard, vous avez un risque immédiat : promos expirées, stock, TVA, arrondis, voire changement de devise.
Un point souvent sous‑estimé en FR/EU : les différences d’arrondi. PrestaShop permet de choisir une méthode d’arrondi (par article, par ligne, sur le total). Si votre ERP ou votre compta n’arrondit pas au même niveau, vous créez des écarts pénibles (surtout sur 100+ lignes). Pour un devis robuste, snapshottez aussi les montants arrondis (au moins au niveau ligne) afin que la conversion en commande reste strictement conforme au PDF envoyé.
Un pattern qui tient en prod consiste à :
- Traiter le devis comme un objet métier (table
ps_quote+ps_quote_row) qui snapshotte :unit_price_tax_excl,unit_price_tax_incl,tax_rate,id_product_attribute,name,reference,discounts,delivery. - Générer un PDF via un template dédié (ne réutilisez pas “facture” si votre compliance n’est pas cadrée).
- Convertir en commande en créant un
Carttechnique puis unOrder, en réinjectant les lignes snapshotées.
Deux détails “terrain” qui rendent le workflow beaucoup plus fiable :
- Date d’expiration + version de prix : stockez
expires_atet un identifiant “version tarifaire” (ex.pricing_source=ERP,pricing_version=2026-08-13T10:00Z). Cela simplifie l’audit quand un client conteste un prix. - État de validation : gardez un statut clair (brouillon → envoyé → accepté → expiré → converti). Même si vous n’en faites pas une machine complexe, cela vous évite des devis “fantômes”.
Côté BO, vous pouvez exposer ce workflow via un module Symfony (PrestaShop 9) ou controller legacy (8.1), mais évitez d’écrire dans ps_specific_price pour chaque devis : c’est tentant (“je crée un prix spécifique client valable 7 jours”), mais vous finissez avec des milliers de lignes expirées et des collisions. Si vous devez valider des numéros de TVA intracom, la Commission européenne met à disposition VIES (service de vérification) — attention à la limitation de débit et au fait que la disponibilité n’est pas garantie en temps réel.
Pour industrialiser la conversion “devis → commande” depuis un SI externe (ERP, CRM, app commerciale), basez-vous sur des APIs plutôt que de simuler des navigations. PrestaShop propose l’API Webservice historique (REST XML) et, en PrestaShop 9, l’Admin API monte en puissance selon les périmètres. Pour les bases : API Webservice PrestaShop : accès CRUD, authentification et bonnes pratiques et, si vous utilisez l’Admin API, verrouillez les scopes : PrestaShop Admin API : sécuriser secret et scopes par client.
Commandes en gros : quick order, import, et contraintes de performance
La commande en gros, c’est rarement “un panier avec 3 lignes”. C’est un CSV de 200 SKUs, un copier‑coller de références, ou une API interne (tablette vendeur). Le cœur PrestaShop sait encaisser de gros paniers, mais vos goulets d’étranglement vont être ailleurs : résolution produit/référence, mises à jour de quantités, recalculs prix, contrôles stock et rendu du panier (templates + hooks + modules).
Une UI de quick order efficace en B2B (sans en faire une usine) inclut souvent :
- un champ multi‑lignes “référence ; quantité” (copier‑coller depuis Excel),
- un import CSV avec un modèle strict (référence, déclinaison, quantité),
- une restitution des erreurs “actionnables” (référence inconnue, quantité < MOQ, produit désactivé, rupture).
Évitez la boucle naïve updateQty() 200 fois en front, avec recalcul complet à chaque itération. Implémentez plutôt un endpoint module qui :
- parse la liste (référence/EAN →
id_product/id_product_attribute), - regroupe par produit/déclinaison,
- applique les quantités en limitant les recalculs (selon version, vous pouvez différer certaines opérations, ou au minimum regrouper et invalider les caches une seule fois).
Sur un cas réel (catalogue ~30k produits, panier B2B ~150 lignes), le simple fait de passer d’une boucle FO “une requête par ligne + recalcul” à une stratégie batch (résolution références en IN (...), puis update) peut faire passer un ajout en panier de 8–12 s à < 2 s côté P95, à stack équivalente. Si vos pages produit/panier sont déjà limites, commencez par les fondamentaux (cache Smarty, CCC, DB) avant d’ajouter une couche quick order : Optimisation PrestaShop : accélérer pages produit et panier et, côté DB, exploitez le slow query log : PrestaShop MySQL : analyser slow query log et optimiser index.
Dernier point : le “bulk” fait exploser les effets de bord des modules (frais de port dynamiques, cadeaux, taxes complexes). Pour éviter les surprises, isolez vos règles : un panier B2B n’a pas forcément les mêmes cart rules qu’un panier B2C. Dans certains cas, créer un shop dédié B2B (multi‑boutique) est plus propre qu’un seul shop surchargé de conditions. Ce n’est pas gratuit (duplication de config, SEO, caches), mais c’est souvent plus maintenable.
Intégration ERP et gouvernance des tarifs : source de vérité, sync et files
En B2B, la question “où vit le prix” est plus structurante que “comment l’afficher”. Si votre ERP est la source de vérité (contrats, remises, franco, conditions), alors PrestaShop doit soit : (a) répliquer les prix dans ps_specific_price, soit (b) calculer à la volée. La réplication est plus rapide côté FO mais demande une pipeline fiable ; le calcul à la volée demande un cache robuste et une tolérance aux pannes.
Une architecture propre ressemble souvent à : ERP → bus/queue → worker → PrestaShop (API). C’est exactement le genre de flux où vous voulez des webhooks, de l’async, et une supervision sérieuse. Pour cadrer : Intégration ERP e‑commerce : architecture API, webhooks et files RabbitMQ et, si vous partez sur un connecteur maison : Intégration API PrestaShop : créer un connecteur ERP sur mesure.
Sur la gouvernance, fixez des règles d’or :
- un seul système écrit les SpecificPrice (sinon collisions),
- un TTL clair pour les prix temporaires (si vous en créez),
- une procédure de purge/archivage (ne laissez pas grossir
ps_specific_pricesans limite), - des tests de non‑régression sur un panier “gros” + un panier “devis” + un panier “B2C”.
Ajoutez une règle très concrète (qui fait gagner du temps en exploitation) : tout flux de prix doit être idempotent. En clair : si vous rejouez le même fichier ou le même message (retry), vous devez retomber sur le même état, sans dupliquer des lignes ni changer des priorités. Cela pousse souvent à préférer une logique “upsert contrôlé” (supprimer/replace par périmètre contractuel) plutôt que “append” illimité.
Si vous avez besoin d’outils d’observabilité pour suivre les erreurs de sync et la qualité des données (références non reconnues, prix manquants), centralisez vos logs et rendez-les requêtables. Même une stack simple (OpenSearch managé, dashboards) vous fera gagner du temps en debug : OVHcloud Managed OpenSearch : moteur NoSQL managé pour logs et recherche.
Industrialiser : tests, CI/CD, sécurité et déploiement sans casse B2B
Dès que vous introduisez des règles B2B, vous introduisez des chemins conditionnels : groupe, pays, devise, quantité, client, période. Sans CI, vous allez “casser le prix” un vendredi soir. Montez au minimum : (1) des fixtures de clients/groupes, (2) des scénarios de panier, (3) des assertions de total TTC/HT, (4) des tests de performance (au moins un smoke test).
Une matrice de test minimaliste, mais très efficace en B2B (à adapter), ressemble à :
| Scénario | Client | Pays | Quantités | Attendu |
|---|---|---|---|---|
| B2C standard | Groupe “Client” | FR | 1 | Prix catalogue + promos B2C |
| Revendeur FR | Groupe “Revendeur FR” | FR | 1 / 10 / 50 | Paliers quantité OK, TVA FR OK |
| Revendeur UE | Groupe “Revendeur UE” | DE/ES… | 10 | TVA intracom (si valide) / sinon TVA locale selon règle |
| Grand compte | id_customer spécifique |
FR | 100+ lignes | Prix contractuels stables + performances |
Si vous ne savez pas par où commencer, une stratégie efficace est de générer des environnements de démo à la demande pour rejouer des scénarios : PrestaShop CI/CD : générer des démos à la demande en minutes et, pour une préprod fidèle : Staging PrestaShop : créer un environnement Docker fidèle à la production.
Sur la sécurité, le B2B augmente la surface : endpoints d’import, quick order, APIs, accès BO élargis (commerciaux), et parfois exposition de prix contractuels. Ne laissez pas un module maison publier un endpoint sans auth solide, rate limit, et journalisation. Ciblez des pratiques concrètes : secrets hors repo, SBOM, WAF si nécessaire, et audits de dépendances. Pour cadrer cette couche : PrestaShop : sécuriser APIs et modules avec SBOM, WAF et CI/CD et, si vous avez des pages blanches en prod, ayez une méthode de diagnostic rapide : PrestaShop page blanche : diagnostiquer l’erreur fatale PHP rapidement.
Enfin, déployez vos changements B2B comme des changements d’infra : migrations DB versionnées, scripts idempotents, rollback plan. Et en B2B, le rollback ne doit pas seulement “remettre le code” : il doit aussi restaurer la cohérence tarifaire (prix, priorités, règles). Si votre shop est déjà conteneurisé ou en route vers Kubernetes, vous pouvez standardiser vos déploiements et limiter les surprises de config (cache, Redis, secrets) : Migration PrestaShop vers Kubernetes : checklist GitOps et pièges courants. Le B2B n’est pas “juste une option BO” : c’est un système de règles, et un système de règles doit être observable, testable et gouverné.




