EA FC, Stripe et premium
Cette page documente les parties implémentées dans le code vérifiées pour EA FC et Stripe.
Sources principales :
convex/matches/ea_stats_sync.ts
convex/matches/ea_score_sync.ts
convex/admin/cron_runners.ts
convex/awards/recipients.ts
convex/stripe/customerIndex.ts
convex/schema.ts
EA FC
Pipeline score sync (matchs)
La synchronisation de score EA est pilotée par le cron runMatchEaScoreSync (convex/admin/cron_runners.ts) :
- il lit les matchs éligibles via
getEligibleMatchesForEaSync,
- traite les
matchIds par lots avec scoreConcurrency,
- appelle
resolveEaScoreAction pour chaque match,
- en parallèle, il prépare
statsSyncCandidates (matchs confirmés sans match_player_stats et sans lignes unmatched),
puis planifie internal.matches.ea_stats_sync.fetchAndPersistEaPlayerStats si nécessaire.
Dans resolveEaScoreAction (convex/matches/ea_score_sync.ts) :
- récupère les matchs EA des deux clubs,
- construit une fenêtre de correspondance autour du kickoff (
DEFAULT_MATCH_TOLERANCE_MINUTES),
- si rien : retry avec fenêtre large
MATCH_HISTORY_DAYS,
- sans candidat ->
persistUnavailable (ea.score.unavailable),
- plusieurs candidats ->
persistAmbiguous (ea.score.ambiguous),
- divergence entre score EA et score soumis / score joueur ->
ea.potential_draw_review_required,
- candidate unique valide ->
persistConfirmedScore (finalizeEaConfirmedScore).
notifyAdmins émet les événements en sortie de sync score :
ea.score.confirmed
ea.score.ambiguous
ea.score.unavailable
ea.score.error
Pipeline stats sync + matching
fetchAndPersistEaPlayerStats (ea_stats_sync.ts) traite les lignes EA d’un match :
- résolution des profils via :
- compo soumise,
- actif de l’effectif,
eaPlayerId,
eaGamertagNormalized.
- ligne matchée : insertion dans
match_player_stats,
- ligne non résolue : insertion/mise à jour dans
match_ea_unmatched_player_stats avec reviewStatus + reasonCode,
- recalc MOTM possible via
replaceMotmAwardRecipientForMatch quand des stats sont matchées (auditReason: 'EA_STATS_PERSISTED').
reviewComparisons (stocké avec la ligne unmatched) regroupe les comparaisons de résolution lineup / active_roster pour le debug admin.
Unmatched review (vérifié)
Types vérifiés :
ACTION_REQUIRED
IGNORED
FRAUD
Codes de raison vérifiés :
AMBIGUOUS_LINEUP_MATCH
AMBIGUOUS_ACTIVE_ROSTER_MATCH
PLAYER_NOT_IN_SUBMITTED_LINEUP
PLAYER_NOT_IN_ACTIVE_ROSTER
LEGACY_UNCLASSIFIED
Règles de classification :
- plusieurs candidats dans la compo =>
ACTION_REQUIRED + AMBIGUOUS_LINEUP_MATCH
- plusieurs candidats actifs résolus =>
ACTION_REQUIRED + AMBIGUOUS_ACTIVE_ROSTER_MATCH
- un seul actif + absent de compo =>
IGNORED + PLAYER_NOT_IN_SUBMITTED_LINEUP
- aucun actif =>
FRAUD + PLAYER_NOT_IN_ACTIVE_ROSTER
autoMapUnmatchedPlayerStats :
- relit uniquement les
ACTION_REQUIRED,
- peut basculer en
match_player_stats si résolution univoque,
- supprime la ligne unmatched après mapping,
- met à jour les compteurs
autoMappedCount, unresolvedCount, ambiguousCount,
- relance le recalcul MOTM (
auditReason: 'EA_STATS_AUTO_MAPPED') quand un mapping auto réussit.
backfillMappedEaStats existe en backfill technique (batch de 50 avec pagination auto).
Éligibilité exacte du score sync
getEligibleMatchesForEaSync sélectionne les matchs :
eaScoreSyncStatus = NOT_ATTEMPTED,
isActive = true,
scheduledAt compris dans la fenêtre de lookback et antérieur à now,
- statut différent de
disputed et cancelled,
- sans résultat déjà complet (
homeScore et awayScore tous deux présents).
Le score sync ne requiert donc pas un statut compétition final avant
interrogation EA. La finalisation applicative reste gérée par les mutations de
résultat, qui écrivent validated.
Le backfill de statistiques EA cible séparément les matchs récents confirmés
(eaScoreConfirmed = true, eaScoreSyncStatus = CONFIRMED, eaMatchRef
présent) qui ne possèdent encore ni match_player_stats ni ligne unmatched.
Stripe premium (vérifié dans convex/stripe/customerIndex.ts)
Statuts et éligibilité
Tableau des statuts connus :
trialing
active
past_due
canceled
unpaid
incomplete
incomplete_expired
paused
Éligibilité premium effective :
- statut membre de l’ensemble
{trialing, active},
livemode cohérent entre subscription et customer résolu,
- filtrage optionnel par
STRIPE_PREMIUM_PRICE_IDS si défini.
Règles STRIPE_PREMIUM_PRICE_IDS :
- variable optionnelle ; parsing en set de chaînes commençant par
price_,
- si variable absente/vides : pas de filtrage par priceId.
livemode :
- pour un owner,
resolveEffectiveLivemode vaut true si au moins un customer Stripe lié est en livemode: true,
- la souscription doit matcher ce mode.
Jobs / synchronisation
reconcilePremiumEntitlements :
- batch par défaut
25,
- max
50,
- pagination + replanification automatique jusqu’à fin.
listForAdmin (admin/modérateurs) :
- filtre options
searchEmail, hasSubscription, livemode,
- scan candidats limité à
MAX_ADMIN_SCAN = 500.
- événements gérés :
customer.created
customer.updated
customer.deleted
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
checkout.session.completed
Last modified on July 16, 2026