Deux visiteurs qui cliquent sur « réserver » à la même seconde pour la dernière place disponible : c’est le scénario que toute billetterie doit gérer correctement, même une billetterie associative légère montée pour un seul événement ponctuel plutôt qu’une plateforme commerciale complète.
Sans précaution particulière, un code naïf qui vérifie le stock disponible puis décrémente la valeur dans deux requêtes séparées laisse une fenêtre de temps, même brève, pendant laquelle deux visiteurs peuvent lire la même valeur de stock avant que l’un des deux ne la modifie. Résultat : une place vendue deux fois.
Un stock stocké en option, pas en métadonnée d’article
Pour un événement unique, un simple enregistrement d’option suffit à représenter le stock disponible, sans avoir besoin d’un type de contenu par place :
update_option( 'wpm_places_disponibles', 150, false );
Le troisième paramètre à false indique à WordPress de ne pas charger cette option automatiquement à chaque affichage de page, ce qui reste pertinent puisque la valeur n’est consultée qu’au moment de la réservation, pas sur chaque page du site.
Décrémenter de façon atomique, directement en SQL
La méthode qui élimine la fenêtre de concurrence consiste à effectuer la lecture et l’écriture du stock en une seule requête SQL, plutôt qu’en deux appels PHP successifs :
global $wpdb;
$table = $wpdb->options;
$places = (int) $_POST['nb_places'];
$lignes_modifiees = $wpdb->query(
$wpdb->prepare(
"UPDATE {$table}
SET option_value = CAST(option_value AS SIGNED) - %d
WHERE option_name = 'wpm_places_disponibles'
AND CAST(option_value AS SIGNED) >= %d",
$places,
$places
)
);
if ( 0 === $lignes_modifiees ) {
wp_send_json_error( array( 'message' => 'Plus assez de places disponibles.' ) );
}
La condition WHERE ... >= %d dans la même requête garantit que la décrémentation n’a lieu que si le stock restant le permet réellement au moment exact de l’exécution, sans dépendre d’une lecture PHP antérieure qui pourrait déjà être obsolète.

Réserver avant de confirmer le paiement
Décrémenter le stock dès le clic sur « réserver » pose un autre problème : un visiteur qui abandonne son paiement bloque une place indéfiniment. La solution consiste à distinguer deux états, avec un délai de grâce court porté par un transient.
Étape 1 : réservation temporaire
set_transient( 'wpm_reservation_' . $jeton, $places, 10 * MINUTE_IN_SECONDS );
Étape 2 : confirmation ou libération
- Si le paiement aboutit, la réservation devient définitive et le stock reste décrémenté
- Si le transient expire sans confirmation, une tâche planifiée réintègre les places au stock disponible
- Si le visiteur annule volontairement, la même routine de libération s’applique immédiatement
Ce que cette architecture ne couvre pas
Cette approche reste pensée pour un événement unique, sans plan de salle ni catégories de places multiples. Une billetterie qui gère plusieurs salles, des tarifs différenciés par catégorie ou des sessions récurrentes justifierait une structure de données plus élaborée, avec une table dédiée par catégorie de place plutôt qu’une simple option globale.
| Étape | Mécanisme | Risque évité |
|---|---|---|
| Vérification du stock | Requête SQL atomique | Survente en cas de clics simultanés |
| Réservation temporaire | Transient avec expiration | Place bloquée indéfiniment |
| Confirmation | Décrémentation déjà appliquée | Double décrémentation |
Sur une billetterie, mieux vaut une requête SQL un peu moins élégante mais atomique qu’un code PHP lisible qui laisse passer une seconde de concurrence entre la lecture et l’écriture du stock.
Notre verdict
Pour un événement associatif ponctuel, cette architecture légère évite d’installer une extension de billetterie complète tout en réglant le seul point réellement critique : ne jamais vendre une place qui n’existe plus. La clé tient dans une opération atomique côté base de données, appuyée par un mécanisme de libération automatique des réservations abandonnées.