Listes de prix
Donnez à vos entreprises et clients leurs propres prix - un pourcentage sur le catalogue plus des prix fixes par produit - pendant que la boutique garde le catalogue en cache.
Une liste de prix - Gold, Silver, Dealer - donne à un groupe d'acheteurs leurs propres prix. Vous l'assignez à une entreprise ou à un client, et cmssy calcule côté serveur le panier, le checkout et les commandes de cet acheteur à partir d'elle. Le catalogue lu par un visiteur ne change pas : vos pages produits restent anonymes et cachables.
Ce que contient une liste
- Un pourcentage de remise sur le prix catalogue, de 0 à 99,99 avec au plus deux décimales. Il s'applique à chaque produit pour lequel la liste n'a pas de prix fixe, et à chacun de ses paliers de quantité. Il est stocké en points de base et calculé en entiers : arrondi au demi supérieur à une unité mineure entière, un produit gratuit reste à 0, et un produit payant ne descend jamais sous 1 unité mineure.
- Des prix fixes par produit, en unités mineures, chacun avec des paliers de quantité optionnels. Un produit avec variantes reçoit un prix fixe par variante, jamais un pour tout le produit. Un prix fixe peut dépasser le prix catalogue - un prix contractuel reste un prix.
Un prix fixe l'emporte sur le pourcentage ; sans l'un ni l'autre, l'acheteur paie le prix catalogue. La devise est toujours celle du produit - une liste change les montants, pas les devises. Un prix fixe pour un produit ou une variante supprimé ensuite ne tarifie plus rien, et la liste le marque comme supprimé pour que vous puissiez l'effacer.
Qui paie quelle liste
Une liste par acheteur, jamais deux cumulées :
- Un client qui appartient à une entreprise paie toujours la liste de l'entreprise - ou le prix catalogue si l'entreprise n'en a pas. cmssy refuse d'assigner une liste directement à ce client : assignez-la à l'entreprise. Une liste que le client avait avant de rejoindre l'entreprise est ignorée tant qu'il en fait partie.
- Un client sans entreprise peut avoir sa propre liste - un VIP, ou un acheteur professionnel isolé.
- Un invité paie le prix catalogue.
Gérer les listes
Les listes se trouvent dans Commerce → Listes de prix. Créez-en une avec un nom et un pourcentage, puis ouvrez-la pour ajouter des prix fixes par produit ou variante. Supprimer une liste efface ses prix fixes et la retire de toutes les entreprises et de tous les clients ; les commandes gardent le nom sous lequel elles ont été calculées.
Assignez une liste sur la page du client, ou sur un enregistrement d'entreprise dans son modèle. Les permissions :
models:edit- créer, modifier et supprimer les listes et leurs prix fixes ;models:viewles lit.customers:manage- assigner une liste à un client ou à une entreprise.customers:viewsuffit pour voir quelles listes existent.
Une synchronisation ERP écrit les prix fixes en masse : priceList.setEntries dans l'API d'administration en écrit jusqu'à 1000 d'un coup, tout ou rien - une ligne invalide refuse l'appel entier, et l'erreur nomme chaque ligne invalide par son index.
Depuis Spotlight et le serveur MCP
Spotlight et le serveur MCP (à partir de 0.108.0) proposent les mêmes opérations, avec les mêmes permissions : list_price_lists, get_price_list, create_price_list, update_price_list, delete_price_list, set_price_list_entry, set_price_list_entries, remove_price_list_entries, set_customer_price_list et set_company_price_list.
Les prix dans une boutique headless
Le prix d'un acheteur est personnel, il n'entre donc jamais dans une requête produit - chaque lecture du catalogue deviendrait propre au membre et impossible à mettre en cache. Le modèle à suivre :
- Rendez et cachez le catalogue de façon anonyme. Les listes et pages produits lisent
public.model.recordsexactement comme pour un visiteur non connecté, avec les prix catalogue. N'indexez jamais une page en cache sur le membre. - Après la connexion, superposez les prix de l'acheteur. Demandez à
account.pricesles produits affichés - 100 éléments au plus par appel - en envoyant leAuthorization: Bearer <accessToken>du membre. Sans jeton de membre, l'appel est refusé. - Ne mettez jamais cette réponse en cache pour un autre visiteur. Pas de cache fetch partagé, pas de CDN. Lisez-la dans une route dynamique ou une server action et injectez les prix dans la page déjà rendue.
query BuyerPrices($workspaceId: ID!, $items: [PriceQueryItem!]!) {
account {
prices(workspaceId: $workspaceId, items: $items) {
recordId
variantKey
currency
price
priceMax
listed
tiers { minQty price }
}
}
}Chaque élément est { recordId, variantKey }, où variantKey est le ProductVariant.id de l'API de diffusion. Omettez-le sur un produit avec variantes pour obtenir une fourchette : price est le plus bas, priceMax le plus haut, et tiers est vide - le prix "à partir de" d'une carte produit. Les montants sont en unités mineures. listed vaut true quand une liste a fixé le prix, pour une étiquette "votre prix". Un produit inconnu ou masqué est omis : faites correspondre les lignes par recordId et variantKey, pas par position.
Pour un badge du type "Vos prix : Dealer Gold", lisez le nom de la liste dans le profil du membre. Il vaut null quand aucune liste ne s'applique, et un membre ne voit jamais que la sienne :
query MyPriceList($workspaceId: ID!) {
account {
profile(workspaceId: $workspaceId) {
priceList { name }
}
}
}Avec @cmssy/core 19.11.0 ou plus récent, getBuyerPrices envoie la requête de prix pour vous.
Panier et checkout
Le panier est tarifé pour celui qui le lit. À chaque lecture et écriture du panier, cmssy recalcule les lignes tarifées pour une autre liste - un invité qui se connecte, un membre qui se déconnecte, une liste assignée ou retirée. unitPrice est le montant qui sera facturé ; snapshot.basePrice est le prix catalogue avant la liste et les paliers, pour un prix barré. Il vaut null sur une ligne ajoutée avant l'existence des listes de prix.
query CartPrices($workspaceId: ID!) {
cart {
get(workspaceId: $workspaceId) {
items {
id
quantity
unitPrice
currentPrice
priceMismatch
snapshot { price basePrice currency }
}
subtotal
}
}
}Modifier le pourcentage ou les prix fixes d'une liste ne recalcule pas les paniers déjà tarifés avec elle. La ligne affiche le nouveau prix dans currentPrice avec priceMismatch: true, et le checkout facture ce qui est dans le panier.
Quand le checkout refuse
Si le recalcul a modifié le panier depuis que l'acheteur l'a vu, cart.checkout refuse plutôt que de facturer un montant que personne n'a affiché. La raison est dans extensions.reason de l'erreur GraphQL :
CART_REPRICED(codeBAD_USER_INPUT) - les prix de l'acheteur ont changé. Le panier est déjà enregistré avec les nouveaux prix : relisez-le, affichez les nouveaux totaux, demandez à l'acheteur de confirmer, puis rappelezcart.checkout.CART_CURRENCY_CHANGED(codeCONFLICT) - une ligne serait désormais tarifée dans une autre devise que le panier ;extensions.itemIdla désigne. Retirez cette ligne et ajoutez de nouveau le produit.
Avec @cmssy/core 19.11.0 ou plus récent, checkoutErrorReason(error) renvoie l'une ou l'autre raison.
Les codes de réduction s'appliquent en plus de la liste : un code est déduit du sous-total calculé selon la liste, et le sous-total minimum d'un code se mesure aussi sur lui.
Commandes
Une commande fige la liste qui l'a tarifée. Son nom apparaît sur la commande dans l'admin et dans get_order, et y reste même si la liste est renommée ou supprimée. Sur chaque ligne de commande :
price- le prix unitaire facturé, liste et palier appliqués.listPrice- le prix unitaire catalogue avant la liste et les paliers. Sans liste, c'est le prix avant paliers, comme avant ; avec un prix contractuel au-dessus du catalogue, il peut être inférieur àprice.
Modifier une commande dans l'admin recalcule ses lignes produits selon les valeurs actuelles de la liste figée, ou aux prix catalogue si cette liste a été supprimée. Une commande manuelle est tarifée selon la liste du client sélectionné.
Étapes suivantes
- Commerce - le panier, le checkout et les commandes qu'une liste tarifie.
- Clients - clients et entreprises, auxquels on assigne une liste.
- Authentification des membres - la connexion et le jeton d'accès dont
account.pricesa besoin.