Skip to content

Reconnaître un achat fait sans compte : compte anonyme, fusion à la connexion et purge (API) #532

Open

Description

@PaulThiberville

Contexte

Epic : #11
API › Conventions (compte anonyme, account_required) · API › Compte (POST /me/merge-anonymous) · Modele-de-Donnees › users · Modele-de-Donnees › route_unlocks · Securite-et-RGPD · D-072 · D-075 · D-099
Maquette : sans objet — API et modèle de données, aucun écran.

Contenu réécrit le 2026-10-20 par le chef de projet, après D-099 (décision d'Ilan du 2026-10-19 sur #531, qui amende D-096) : un achat fait sans compte appartient à un compte anonyme Firebase, créé par l'app juste avant sa première opération du store faite sans compte (achat ou restauration) ; son uid est l'identifiant RevenueCat. route_unlocks ne change pas : user_id reste non nul, la clé reste (user_id, route_id), plus de revenuecat_app_user_id sur la ligne ni de rattachement au logIn. Le contenu précédent (migration de route_unlocks) est abandonné.

Bloqué par #341 (vérifié le 2026-10-20) : la fusion déplace des lignes route_unlocks qui portent source et store_transaction_id, et un droit Premium qui porte plan_source, colonnes ajoutées par la PR #499. La migration de ce ticket suit celle de la PR #499.

Comportement attendu

  • Migration Drizzle versionnée sur users : is_anonymous (boolean, défaut false, lu dans le jeton, fournisseur anonymous, repasse à false quand l'app lie une identité par linkWithCredential) ; merged_into_id (uuid nullable, compte connecté dans lequel le compte anonyme a été fusionné) ; revenuecat_app_user_id = firebase_uid, compte anonyme compris.
  • Authentification (apps/api/src/auth/plugin.ts) : aujourd'hui tout jeton anonyme est refusé en 401 « Sign in required », y compris sur optionalAuth (constaté sur main le 2026-10-20). À la place : optionalAuth accepte un jeton anonyme, et canAccessRoute reste la seule règle de droit pour la fiche et la recherche, réponse privée (D-075) ; requireAuth et requireRole appelés avec un jeton anonyme répondent 403 account_required, nouveau code stable. Aucune ligne users créée pour un jeton anonyme qui n'a fait aucun achat, hors la règle du webhook ci-dessous.
  • POST /me/merge-anonymous { anonymousIdToken } (compte connecté) : l'API vérifie le jeton (signé par Firebase, fournisseur anonymous, non expiré), puis déplace en une transaction les déblocages et le droit Premium du compte anonyme vers le compte connecté (décision d'Ilan du 2026-09-29 : les achats passent au compte connecté, téléphone partagé compris), sans doublon quand le compte avait déjà le parcours ; un droit Premium offert (plan_source = gift) n'est pas écrasé ; pose merged_into_id. Idempotent : un second appel avec le même jeton répond 200 sans rien déplacer.
  • Webhook RevenueCat (service partagé avec Brancher RevenueCat et construire l'écran Premium (E-17) #67) : ligne users anonyme créée au premier événement si elle n'existe pas encore ; un événement qui nomme un compte fusionné s'applique à merged_into_id ; TRANSFER déplace les achats comme la fusion.
  • Purge : comptes anonymes sans achat supprimés 7 jours après leur création (ligne users et compte Firebase, par l'Admin SDK) ; l'app qui reçoit alors un 401 oublie le compte et repart sans jeton. Le nettoyage automatique des comptes anonymes de Firebase (Identity Platform) reste désactivé. Conservation d'un compte anonyme qui porte un achat : en attente de la décision d'Ilan (Securite-et-RGPD, « à fixer »).

Critères d'acceptation

  • Un jeton anonyme qui a débloqué un parcours lit la fiche et la card en entier (isLocked: false) ; la même requête sans jeton les reçoit verrouillées (tests API)
  • Un jeton anonyme sur une route qui demande un compte répond 403 account_required (test)
  • Fusion : déblocages et droit Premium passent au compte connecté, sans doublon, plan_source = gift intact ; un jeton non anonyme, d'un autre projet Firebase ou expiré est refusé (tests)
  • Un événement RevenueCat sur un compte anonyme fusionné s'applique au compte connecté ; TRANSFER déplace les achats (tests du webhook)
  • Purge : un compte anonyme sans achat de plus de 7 jours est supprimé, un compte anonyme qui porte un achat est gardé (test)
  • packages/shared porte account_required et le schéma de POST /me/merge-anonymous, sans seconde définition
  • API, Modele-de-Donnees et le code disent la même chose

Notes techniques

  • Niveau 3 : authentification et droits d'achat. Retour arrière à décrire dans la PR.

Activity

  1. added this to the v0.4 milestone on Oct 19, 2026
  2. added
    status:readySpec complète, prêt à coder
    and removed
    status:blockedBloqué, voir commentaire ⛔
    on Oct 19, 2026
  3. added
    status:blockedBloqué, voir commentaire ⛔
    and removed
    status:readySpec complète, prêt à coder
    on Oct 19, 2026
  4. added and removed on Oct 19, 2026
  5. IlanAMG commented on Oct 19, 2026

    @IlanAMG
    Member

    Chef de projet (point du 19/10/2026) : priorité P1 → P0. Il bloque #67 (P0). Chaîne #52 → #341 → #532 → #67 (P0, v0.4, RevenueCat) : un bloqueur ne peut être ni moins prioritaire ni planifié après ce qu'il bloque.

  6. IlanAMG commented on Oct 20, 2026

    @IlanAMG
    Member

    Chef de projet (point du 20/10/2026) : D-099 (décision d'Ilan sur #531) change le contenu de ce ticket : plus de migration de route_unlocks (user_id reste non nul), à la place users.is_anonymous, users.merged_into_id, POST /me/merge-anonymous, 403 account_required, webhook et purge à 7 jours. Le corps actuel (D-096) est périmé : à réécrire par MANAGER avant que CODE ne le prenne. La raison du blocage par #341 (migration sur la même table que la PR #499) tombe ; la dépendance est laissée en place en attendant la décision d'Ilan (point du jour).

  7. changed the title [-]Enregistrer un déblocage acheté sans compte et le rattacher au compte (route_unlocks)[/-] [+]Reconnaître un achat fait sans compte : compte anonyme, fusion à la connexion et purge (API)[/+] on Oct 20, 2026
  8. IlanAMG commented on Oct 20, 2026

    @IlanAMG
    Member

    Chef de projet (2026-10-20) : corps et titre réécrits après D-099. La dépendance à #341 est gardée : la fusion lit route_unlocks.source et users.plan_source, ajoutés par la PR #499. Le correctif de l'authentification facultative (jeton anonyme refusé en 401 sur main, apps/api/src/auth/plugin.ts) est porté ici.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions