Que se passe-t-il quand deux visiteurs cliquent sur le même siège de la même rangée à la même seconde, au moment précis où s’ouvrent les ventes d’un festival attendu ? C’est exactement le scénario que doit anticiper une billetterie à places numérotées : contrairement à un billet générique dont le stock se décrémente simplement, une place numérotée est un identifiant unique qui ne peut jamais être vendu deux fois, quelle que soit la charge simultanée sur le serveur. Ce billet ne traite pas le paiement fractionné ni le remboursement, mais l’architecture de réservation elle-même.
Modéliser la place comme un enregistrement individuel
La première décision structurante consiste à ne jamais représenter une place numérotée comme une simple variation de produit WooCommerce avec un stock décrémenté : cette approche ne permet pas de savoir *quel* siège précis a été vendu, seulement *combien*. Une table personnalisée, créée via dbDelta() lors de l’activation d’une extension maison, stocke chaque place individuellement avec son statut :
CREATE TABLE {$wpdb->prefix}festival_places (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
zone VARCHAR(50) NOT NULL,
rangee VARCHAR(10) NOT NULL,
siege VARCHAR(10) NOT NULL,
statut ENUM('libre','reservee','vendue') DEFAULT 'libre',
commande_id BIGINT UNSIGNED NULL,
verrouille_jusqu_a DATETIME NULL,
UNIQUE KEY zone_rangee_siege (zone, rangee, siege)
) {$wpdb->get_charset_collate()};
Cette structure permet d’interroger simplement l’état d’une zone entière, de repérer les places libres par rangée, et surtout de poser une contrainte d’unicité sur la combinaison zone-rangée-siège, qui empêche toute duplication au niveau même de la base de données.
Poser un verrou temporaire à la sélection

Quand un visiteur sélectionne un siège, la place ne doit pas être vendue immédiatement mais réservée temporairement, le temps qu’il termine son paiement. Ce verrou passe par une mise à jour conditionnelle en base, atomique, pour éviter toute course entre deux visiteurs qui cliqueraient sur le même siège simultanément :
function wpm_reserver_place( $place_id, $session_id ) {
global $wpdb;
$lignes_affectees = $wpdb->query( $wpdb->prepare(
"UPDATE {$wpdb->prefix}festival_places
SET statut = 'reservee', verrouille_jusqu_a = %s
WHERE id = %d AND statut = 'libre'",
gmdate( 'Y-m-d H:i:s', time() + 900 ), // verrou de 15 minutes
$place_id
) );
return $lignes_affectees === 1; // true seulement si la place était réellement libre
}
La clause WHERE statut = 'libre' combinée au test du nombre de lignes affectées est ce qui garantit l’absence de double réservation : si deux requêtes arrivent en même temps sur la même place, MySQL ne permet qu’à une seule d’affecter réellement une ligne, l’autre reçoit zéro ligne modifiée et doit alors proposer un autre siège au visiteur.
Libérer automatiquement les verrous expirés
Une tâche planifiée via Action Scheduler, exécutée toutes les minutes, repère les places dont le verrou a expiré sans paiement confirmé et les remet en statut libre :
function wpm_liberer_places_expirees() {
global $wpdb;
$wpdb->query(
"UPDATE {$wpdb->prefix}festival_places
SET statut = 'libre', verrouille_jusqu_a = NULL, commande_id = NULL
WHERE statut = 'reservee' AND verrouille_jusqu_a < NOW()"
);
}
add_action( 'wpm_liberation_places', 'wpm_liberer_places_expirees' );
Finaliser la vente au paiement confirmé
Ce n'est qu'à la confirmation réelle du paiement, sur le hook woocommerce_payment_complete, que le statut passe définitivement à « vendue », avec l'identifiant de commande associé. Toute tentative de finaliser une place dont le verrou aurait expiré entre-temps doit échouer proprement et rediriger le client vers une nouvelle sélection, plutôt que de forcer artificiellement la vente d'une place potentiellement déjà reprise par un autre visiteur.
Gérer le pic d'ouverture des ventes
- Désactiver tout cache de page sur l'interface de sélection des sièges pendant la fenêtre d'ouverture.
- Prévoir une file d'attente simple si l'affluence attendue dépasse largement la capacité du serveur.
- Charger la disponibilité des places via une requête légère et fréquente plutôt qu'un rechargement complet de page.
Testez la contrainte d'unicité en base avec un script qui simule plusieurs requêtes simultanées sur le même siège avant l'ouverture réelle : c'est le seul moyen de vérifier que le verrou tient réellement sous charge.
En résumé
Une billetterie à places numérotées fiable repose sur une structure de données dédiée, une contrainte d'unicité posée directement en base, et un mécanisme de verrouillage temporaire avec libération automatique. Cette architecture, plus exigeante qu'un simple stock WooCommerce, est la seule qui élimine réellement le risque de double vente lors des pics d'affluence d'une ouverture de billetterie très attendue.