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).
Point à vérifier
- La portée des règles métier autour du statut du match (par ex. validation finale selon l’état compétition) n’est pas détaillée dans ces modules.
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 June 24, 2026