Le WordPress d'aujourd'hui, décodé pour les développeurs

E-commerce

Une billetterie de festival avec des places numérotées sous WooCommerce

Vendre des places numérotées par zone pour un festival, sans double vente lors des pics d'affluence à l'ouverture des ventes. Architecture technique détaillée, du panier au verrou de réservation.

Par Clément Hadrot • 7 décembre 2024 • 5 min de lecture • Aucun commentaire
Une billetterie de festival avec des places numérotées sous WooCommerce

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

L'essentiel à retenir : Chaque place numérotée doit être un enregistrement individuel, jamais un simple compteur de stock ; Un verrou de réservation temporaire évite la double vente pendant le paiement ; La structure de données sépare zone, rangée et siège pour rester interrogeable simplement

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.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi