Skip to main content

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