mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-06-28 01:53:40 +02:00
docs(knowledge): capitalisation backend — intégration du triage local (mai-juin 2026)
Triage et intégration des propositions backend du buffer 95_a_capitaliser.md (lot local RL799_V2 + app-alexandrie, mai-juin 2026), distinct de la capitalisation remote antérieure (triage 2026-05-02). ~73 entrées intégrées sur knowledge/backend/, dont : - patterns/auth.md : série "membrane d'auth fédérée BFF/OIDC" (9 patterns) + jose algo whitelist - patterns/prisma.md : recette fusionnée "Migration String/Int → enum" (backfill + Cas A/B/C), row réactivable, endpoint replace atomique, updateMany conditionnel, etc. - risques/general.md : 19 risques (epoch s vs ms, keepAliveTimeout=0, upsert+filtre liste, fail-safe catch-all, retrait asymétrique front/back, anti-énumération rate-limit, etc.) - patterns/general, async, nestjs, contracts, tests + risques/auth, contracts, prisma, redis, stripe, tests - compléments d'entrées existantes (authorize-after-fetch, P3014, cursor opaque, DI swc, Stripe v20...) - README patterns/risques mis à jour Doublons internes corrigés en relecture (suppression-champ .map() → general seul ; e2e DB-based → tests.md seul). Doublons hors backend / entrées projet / rejets non intégrés. Source 95_a_capitaliser.md non purgée à ce stade (purge en fin de capitalisation complète). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,10 @@
|
||||
- Utiliser `subscription.current_period_end` (timestamp) pour la fin de période courante
|
||||
- Ajouter un test sur un événement webhook/Subscription qui vérifie la date persistée
|
||||
|
||||
### Nuance SDK v20 (API 2025-03-31.basil+) : `current_period_end` est par ITEM, plus à la racine
|
||||
|
||||
Depuis le SDK Stripe v20, `Subscription.current_period_end` n'existe **plus** au niveau racine : lire `subscription.items.data[i].current_period_end` (prendre le **max** des items pour borner au plus tard). Symptôme : `currentPeriodEnd` revient systématiquement `null` → un abo « ACTIVE » sans borne de période reste ouvert indéfiniment (abo « zombie ») si un event de renouvellement est manqué. Garde-fou complémentaire : `isActive = status === 'ACTIVE' && currentPeriodEnd != null && currentPeriodEnd > now`. Valider sur un event Stripe réel (l'API effective dépend de la clé/compte).
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-stripe-list-has-more"></a>
|
||||
@@ -114,3 +118,50 @@
|
||||
- Si `processing` détecté (concurrent) : attendre brièvement la transition `processed`, sinon répondre **non-2xx** (force retry provider)
|
||||
- Ne jamais passer à `processed` sans preuve d'un traitement effectif
|
||||
- Contexte technique : Stripe / NestJS — 09-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-refund-lie-user-produit"></a>
|
||||
## Remboursement lié à (user, produit) au lieu de la TRANSACTION (PaymentIntent)
|
||||
|
||||
### Risques
|
||||
|
||||
- Un garde-fou « ne pas ré-accorder l'accès si déjà remboursé » identifié par `(userId, productId)` casse le cas « rembourser puis racheter » : l'ancien refund contamine le nouvel achat
|
||||
- Un RACHAT légitime (nouveau paiement, nouveau PaymentIntent) du même produit par le même user est bloqué → client qui re-paie sans accès (perte de revenu + incident)
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Garde-fou d'idempotence/révocation indexé sur `(userId, packId)` plutôt que sur le `paymentIntentId`
|
||||
- Un `RefundRecord` orphelin (refund arrivé avant `completed`) qui bloque tout achat futur du même produit
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Un remboursement concerne UNE transaction précise, pas une relation `(user, produit)` durable. La clé d'un refund et de son garde-fou d'idempotence/ordre = le `paymentIntentId` (ou l'id de charge), **jamais** `(user, produit)`.
|
||||
- Propager le `paymentIntentId` depuis `checkout.session.completed` (`session.payment_intent`) jusqu'au garde-fou.
|
||||
- Corollaire (arrivée désordonnée refund-avant-completed via un `RefundRecord`) : matcher ce record par `paymentIntentId` à la création du `UserPack`, sinon il bloque tout achat futur du même produit.
|
||||
- **Test obligatoire** : achat → refund → rachat (nouveau PI) → accès accordé.
|
||||
|
||||
- Contexte technique : Stripe / refund / webhooks — app-alexandrie 02-06-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-refund-consommation-visionnage-reel"></a>
|
||||
## Éligibilité « refund si peu consommé » mesurée sur la validation, pas le visionnage réel
|
||||
|
||||
### Risques
|
||||
|
||||
- Une politique de remboursement bornée par la consommation (ex : « < 20 % consommé ») qui mesure un drapeau de VALIDATION EXPLICITE (clic « terminer » → state `COMPLETED`) est contournable
|
||||
- L'utilisateur regarde 100 % du contenu sans cliquer « valider » → `completionPct = 0` → remboursable malgré tout le contenu consommé (open-bar)
|
||||
|
||||
### Symptômes
|
||||
|
||||
- AC qui cite `maxWatchedPct` mais implémentation qui compte `state === 'COMPLETED'`
|
||||
- Divergence d'oracle entre le badge UI « remboursable » et ce que le serveur accepte réellement
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Mesurer la consommation EFFECTIVE (progression vidéo `maxWatchedPct >= seuil`, lecture réelle), JAMAIS un drapeau de validation cliqué par l'utilisateur (`COMPLETED`).
|
||||
- Règle de seuil : leçon vidéo consommée dès `maxWatchedPct >= 90` (seuil de complétion vidéo), leçon texte dès `COMPLETED`.
|
||||
- Garder UNE seule source de mesure partagée par la décision serveur ET le badge UI « remboursable » (sinon divergence d'oracle).
|
||||
|
||||
- Contexte technique : Stripe / refund / consommation contenu — app-alexandrie 04-06-2026
|
||||
|
||||
Reference in New Issue
Block a user