Skip to content

Events & Pricing

Un événement (Event) appartient à un business club (BusinessClub) et applique deux tarifs distincts selon que l'entreprise inscrite est adhérente de ce club ou non. Cette page couvre : comment rejoindre un club, les deux flux d'inscription à un événement (gratuit / payant), le calcul du prix, l'ouverture de billetterie en deux temps, les règles d'accès non-adhérent et le masquage de la liste des participants.

Rejoindre un business club

L'adhésion à un club se fait via un code d'adhésion (membershipCode, 8 caractères, généré côté serveur). Ce code n'est jamais exposé en lecture par l'API : aucune query ne le retourne, y compris pour un membre du club.

Pour rejoindre un club, appelez la mutation joinBusinessClubWithCode(companyId, code), qui crée une CompanyBusinessClubMembership avec un statut parmi ACTIVE, EXPIRED, SUSPENDED. Le code doit vous être communiqué hors-API (par le club lui-même) — il n'y a pas de moyen de le découvrir par une query.

Tout le reste de cette page — tarif appliqué, ouverture de billetterie, quota, visibilité des participants — dépend de l'existence d'une CompanyBusinessClubMembership avec status: ACTIVE entre l'entreprise inscrite et le businessClubId de l'événement.

Deux flux d'inscription distincts

Il existe deux mutations différentes pour participer à un événement, et le choix entre les deux n'est pas indiqué explicitement par un champ dédié sur l'événement — c'est une règle d'usage basée sur le prix :

MutationQuand l'utiliserRésultat
registerToEventÉvénement gratuit pour l'acheteur (memberPrice = 0 s'il est adhérent, ou nonMemberPrice = 0 sinon)EventRegistration créée directement en statut CONFIRMED, aucun paiement
createEventOrderÉvénement payant, loges (suites), ou places invitésEventOrder (panier) + EventTicket(s) en statut HELD, redirection Stripe, confirmation asynchrone

Les deux flux appliquent les mêmes contrôles d'accès, dans le même ordre, avant toute création d'inscription ou tout calcul de prix : ouverture de la billetterie selon le statut adhérent, puis éligibilité à participer (événement passé, complet, réservé aux adhérents, quota non-adhérent). Les codes d'erreur correspondants sont donc à traiter indifféremment sur l'une comme sur l'autre — voir Conventions API — catalogue des codes d'erreur.

registerToEvent — flux gratuit

Vérifications appliquées, dans l'ordre :

  1. Que l'utilisateur n'est pas déjà inscrit avec une autre entreprise (ALREADY_REGISTERED_OTHER_COMPANY) — s'il est déjà inscrit avec la même entreprise, l'appel est idempotent et renvoie l'inscription existante plutôt que d'échouer (double-clic, rejeu de commande).
  2. L'ouverture de la billetterie selon le statut adhérent/non-adhérent (voir section dédiée) — sinon TICKETS_NOT_OPEN.
  3. L'éligibilité à participer (événement passé, complet, réservé aux adhérents, quota) — voir « Contrôle d'accès non-adhérent ».

Si toutes les vérifications passent, l'inscription est créée directement en CONFIRMED — il n'y a pas d'étape PENDING intermédiaire pour ce flux gratuit. Le statut adhérent (isMemberAtRegistration) est figé à ce moment précis.

createEventOrder — flux payant

L'appelant fournit eventId et une liste de lines, chacune avec :

  • scope : GENERAL (place standard), SUITE_SEAT (siège dans une loge) ou SUITE_WHOLE (loge entière) — enum OrderTicketScope.
  • holderType : SELF_USER, OTHER_USER ou GUEST — enum OrderTicketHolderType.
  • Pour un invité (GUEST) : guestName/guestEmail optionnels.

buyerCompanyId n'est pas un argument de la mutation : il est résolu côté serveur à partir de l'utilisateur authentifié, vous n'avez aucun moyen de le forcer.

Les mêmes contrôles d'accès que registerToEvent s'appliquent avant tout calcul de prix : ouverture de la billetterie (TICKETS_NOT_OPEN), puis éligibilité de participation (MEMBER_ONLY, NON_MEMBER_QUOTA_EXHAUSTED, EVENT_PAST, EVENT_FULL, ALREADY_REGISTERED).

Une commande crée une EventOrder (le panier) et un EventTicket par ligne, chaque ticket en statut HELD avec holdExpiresAt = now + 30 minutes — passé ce délai, le ticket expire et libère la place/le siège de loge réservé. Les deux flux (gratuit et payant) se sérialisent côté serveur sur la capacité de l'événement : ils ne peuvent pas ensemble dépasser maxAttendees.

Deux issues possibles :

  • Total à 0 (ex. loge à prix nul, ou combinaison qui retombe à zéro) : la commande est confirmée immédiatement, sans passer par Stripe — clientSecret vaut null.
  • Total > 0 : une session de paiement Stripe est créée et un clientSecret est renvoyé pour que le client finalise le paiement côté front. La confirmation de la commande (passage PENDINGPAID, tickets HELDCONFIRMED) se fait de façon asynchrone à réception du webhook Stripe, pas en retour direct de createEventOrder. Ré-interrogez la commande après retour sur votre successUrl plutôt que de supposer qu'elle est déjà payée.

Tarification

Prix adhérent / non-adhérent

Chaque événement porte deux prix en centimes (BigInt) : memberPrice et nonMemberPrice, tous deux à 0 par défaut. Une place GENERAL est facturée memberPrice si l'acheteur est adhérent actif du club organisateur, nonMemberPrice sinon.

Le statut adhérent de l'acheteur est figé au moment de la commande, pas recalculé a posteriori : il est déterminé une seule fois et stocké tel quel sur l'EventOrder (isBuyerMember). Une entreprise qui rejoint le club juste après avoir passé commande ne voit pas son tarif recalculé rétroactivement, et inversement une expiration d'adhésion après achat ne change rien à une commande déjà passée.

Loges (suites) — pas de tarif adhérent

Les loges (EventSuite, sièges vendus en SUITE_SEAT ou en bloc SUITE_WHOLE) ont un prix unique, indépendant du statut adhérent — wholePrice/seatPrice par loge, sans distinction membre/non-membre. Un événement n'autorise les lignes de type loge que si Event.allowSuites est activé.

Frais de service — 5 %

Un frais de service de 5 % du sous-total est ajouté systématiquement sur les commandes payantes : serviceFee = (subtotal × 5) / 100, calcul en entier sur centimes. Il n'est paramétrable ni par événement ni par club — c'est une constante. Le total facturé est subtotal + serviceFee.

Le flux registerToEvent (gratuit) ne passe jamais par ce calcul : il n'y a pas de frais de service sur une inscription gratuite.

Billetterie en deux temps

Chaque événement porte deux dates d'ouverture distinctes :

  • ticketOpeningDate — ouverture pour les adhérents du club organisateur.
  • publicTicketOpeningDate — ouverture pour tout le monde (adhérents et non-adhérents).

La vérification se fait à l'inscription, sur les deux flux : si l'acheteur est adhérent actif du club de l'événement, l'inscription est possible dès ticketOpeningDate ; sinon il faut attendre publicTicketOpeningDate. Avant l'heure d'ouverture applicable, l'appel échoue avec extensions.code: "TICKETS_NOT_OPEN" et extensions.params.opensAt renseigné.

En pratique, publicTicketOpeningDate est généralement postérieure ou égale à ticketOpeningDate (fenêtre d'avantage adhérent), mais ce n'est pas une contrainte imposée par le schéma — les deux dates s'appliquent séparément selon le statut de l'acheteur.

Contrôle d'accès non-adhérent

allowNonMembers

Par défaut (false), un événement est réservé aux adhérents du club organisateur (Event.allowNonMembers). Si un non-adhérent tente de s'inscrire — via registerToEvent ou createEventOrder — sur un événement où ce flag est à false, l'appel échoue avec extensions.code: "MEMBER_ONLY".

Quota non-adhérent par club

Quand allowNonMembers est activé, le nombre de non-adhérents pouvant s'inscrire à des événements d'un club donné est plafonné par BusinessClub.nonMemberEventQuota (0 par défaut). Le quota consommé se calcule en comptant, pour ce club spécifique, les EventRegistration de l'entreprise avec isMemberAtRegistration: false et un statut CONFIRMED ou ATTENDED :

remaining = max(0, businessClub.nonMemberEventQuota - eventsUsed)

Quota épuisé → extensions.code: "NON_MEMBER_QUOTA_EXHAUSTED", sur registerToEvent comme sur createEventOrder. Ce compteur est par club, pas global à la plateforme : une entreprise non-adhérente peut avoir consommé son quota sur le club A et rester éligible sur le club B.

Quota d'abonnement non appliqué

Le modèle prévoit un quota d'événements gratuits lié au tier d'abonnement de l'entreprise (STARTER/BUSINESS/PREMIUM). Ce quota n'est pas appliqué aujourd'hui : le tier d'abonnement ne conditionne ni l'accès à un événement ni son tarif. La seule limite effective pour un non-adhérent reste le quota par club décrit ci-dessus (nonMemberEventQuota). N'implémentez pas de logique côté intégration qui suppose un plafond différent par tier d'abonnement.

Liste des participants

L'accès à la liste des inscrits (Event.registrations) dépend du flag Event.hideParticipantsFromNonMembers (true par défaut) et du statut du viewer courant :

  • Les inscriptions CANCELLED, et les inscriptions marquées isVisibleInParticipants: false par leur propriétaire (sauf pour lui-même), sont systématiquement exclues.
  • Viewer anonyme (pas de token) : la liste est toujours masquée, quel que soit hideParticipantsFromNonMembers.
  • Si hideParticipantsFromNonMembers est à false : la liste complète est renvoyée à tout viewer authentifié.
  • Si hideParticipantsFromNonMembers est à true (comportement par défaut) : la liste complète n'est renvoyée que si le viewer est lui-même inscrit à l'événement, ou s'il est adhérent actif du club organisateur. Dans tous les autres cas, la liste est masquée.

Le masquage ne retire pas les entrées : il vide les champs identifiants de chaque inscription masquée — id remplacé par un identifiant synthétique, firstName/lastName/email/phone/name/description/address/companyCity vidés, et l'avatar/logo remplacés par leur version floutée (avatarBlur/logoBlur) si disponible. Le nombre de participants reste donc visible même masqué ; seule l'identité est cachée.

Récapitulatif des statuts

EnumValeursPortée
EventStatusDRAFT, PUBLISHED, ONGOING, COMPLETED, CANCELLEDCycle de vie de l'événement
EventRegistrationStatusPENDING, CONFIRMED, ATTENDED, NO_SHOW, CANCELLED, REFUSEDInscription (flux gratuit et places issues d'une commande payée)
EventOrderPaymentStatusPENDING, PAID, REFUNDED, CANCELLEDCommande payante
EventTicketStatusHELD, CONFIRMED, EXPIRED, CANCELLEDTicket individuel issu d'une commande — HELD pendant la fenêtre de 30 min avant paiement

registerToEvent ne passe jamais par EventOrder/EventTicket : il écrit directement une EventRegistration en CONFIRMED. createEventOrder produit toujours une EventOrder + des EventTicket, indépendamment du montant (y compris à 0).