# V0.603 Release Scope — Transfer automatique, Commission & Stabilisation **Statut** : En cours **Phase** : 6+ **Prérequis** : v0.602 (taguée) **Date cible** : TBD **Estimation** : ~4 sprints (20 jours ouvrés) **Précédente** : [v0.602](archive/V0_602_RELEASE_SCOPE.md) --- ## 1. Objectif Boucler la chaîne de paiement vendeur en implémentant le **transfert automatique Stripe Connect** après paiement réussi (reporté depuis v0.602), ajouter la **commission plateforme configurable**, nettoyer la **dette technique** (triage des 210+ TODOs, archivage docs obsolètes), et renforcer les **tests du flux paiement**. --- ## 2. État actuel (post-v0.602) | Composant | État | Détail | |-----------|------|--------| | **Stripe Connect onboarding** | ✅ Livré v0.602 | POST /sell/connect/onboard, GET /sell/balance | | **seller_stripe_accounts** | ✅ Livré v0.602 | Migration 114, modèle GORM | | **StripeConnectService.CreateTransfer** | ✅ Code existant | Méthode implémentée mais **jamais appelée** | | **ProcessPaymentWebhook** | ⚠️ Incomplet | Crée les licences mais **ne déclenche pas les transferts** | | **Commission plateforme** | ❌ Absente | Aucune config ni calcul de commission sur ventes marketplace | | **Suivi des transferts** | ❌ Absent | Pas de table ni log des transferts effectués | | **TODOs backend** | ⚠️ 210+ | Triage nécessaire (supprimer obsolètes, transformer restants en tickets) | | **Docs obsolètes** | ⚠️ 82 fichiers | Archiver les docs pre-v0.501 dans docs/archive/ | --- ## 3. Lots ### Lot T1 — Transfer automatique après vente (Stripe Connect) **Objectif** : Lorsqu'un paiement réussit (webhook Hyperswitch `succeeded`), transférer automatiquement la part vendeur vers son compte Stripe Connect. | # | Tâche | Fichiers impactés | Effort | |---|-------|--------------------|--------| | T1-01 | Config commission plateforme — `PLATFORM_FEE_RATE` (default 0.10 = 10%) | `internal/config/config.go`, `.env.example` | S | | T1-02 | Migration `115_seller_transfers.sql` — table de suivi des transferts | `migrations/115_seller_transfers.sql` | S | | T1-03 | Modèle `SellerTransfer` — GORM model + BeforeCreate UUID hook | `internal/core/marketplace/models.go` | S | | T1-04 | Interface `TransferService` — abstraction pour le service de transfert | `internal/core/marketplace/service.go` | S | | T1-05 | Option `WithTransferService` — injection optionnelle dans marketplace.Service | `internal/core/marketplace/service.go` | S | | T1-06 | Logique transfer dans `ProcessPaymentWebhook` — après création licences, regrouper items par vendeur, calculer montant net, appeler CreateTransfer, enregistrer SellerTransfer | `internal/core/marketplace/service.go` | L | | T1-07 | Endpoint GET /sell/transfers — historique des transferts pour un vendeur | `internal/handlers/sell_handler.go`, routes | M | | T1-08 | Frontend : SellerTransfersCard — liste des transferts dans SellerDashboard | `apps/web/src/features/seller/` | M | | T1-09 | MSW handlers + Story — mock GET /sell/transfers | `apps/web/src/mocks/handlers.ts`, stories | S | | T1-10 | Tests unitaires — ProcessPaymentWebhook avec transfert, multi-vendeur, vendeur sans Connect | `internal/core/marketplace/process_webhook_test.go` | M | ### Lot DT1 — Dette technique & Nettoyage **Objectif** : Réduire la dette technique accumulée, archiver les docs obsolètes. | # | Tâche | Fichiers impactés | Effort | |---|-------|--------------------|--------| | DT1-01 | Triage TODOs backend — supprimer obsolètes, convertir pertinents en issues GitHub | `veza-backend-api/internal/**` | M | | DT1-02 | Archiver docs pre-v0.501 — déplacer dans docs/archive/ les scopes/plans obsolètes | `docs/*.md` → `docs/archive/` | S | | DT1-03 | Mettre à jour PAYOUT_MANUAL.md — remplacer « procédure manuelle » par « transfert automatique v0.603 » | `docs/PAYOUT_MANUAL.md` | S | | DT1-04 | Nettoyer code mort marketplace — imports inutilisés, variables non référencées | `internal/core/marketplace/` | S | ### Lot QA3 — Tests & Documentation de release **Objectif** : Valider le flux transfer E2E, documenter, tagger. | # | Tâche | Fichiers impactés | Effort | |---|-------|--------------------|--------| | QA3-01 | Test E2E transfer — flow complet : product → order → webhook succeeded → transfer créé → SellerTransfer enregistré | `internal/core/marketplace/` ou integration tests | M | | QA3-02 | Test multi-vendeur — commande avec 2 produits de vendeurs différents → 2 transfers distincts | `internal/core/marketplace/process_webhook_test.go` | M | | QA3-03 | Test vendeur sans Connect — paiement réussit, licences créées, transfer ignoré (log warning) | `internal/core/marketplace/process_webhook_test.go` | S | | QA3-04 | Smoke test v0.603 — checklist complète | `docs/SMOKE_TEST_V0603.md` | S | | QA3-05 | Mise à jour PROJECT_STATE, FEATURE_STATUS, CHANGELOG | `docs/` | S | | QA3-06 | Rétrospective v0.603, archivage scope, placeholder v0.604, tag | `docs/`, Git | S | --- ## 4. Hors scope v0.603 | Élément | Version cible | |---------|---------------| | Go Live (streaming vidéo) | v0.703 | | 2FA SMS / Passkeys | v0.104 | | IaC (Terraform/Pulumi) | v0.801 | | Migration React 19 | v0.604 | | Payout scheduling (cron récurrent) | v0.604 | | Dashboard transferts admin (vue admin des transferts plateforme) | v0.604 | --- ## 5. Détail technique — Lot T1 ### 5.1 Commission plateforme ```go // config.go PlatformFeeRate float64 // PLATFORM_FEE_RATE, default 0.10 (10%) ``` Le calcul pour chaque item : - `sellerAmount = item.Price * (1 - platformFeeRate)` - Arrondi à l'unité inférieure (centimes) pour éviter les fractions - Les montants sont en centimes (int64) pour Stripe ### 5.2 Migration 115_seller_transfers.sql ```sql CREATE TABLE IF NOT EXISTS seller_transfers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), seller_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE, stripe_transfer_id VARCHAR(255), amount_cents BIGINT NOT NULL, platform_fee_cents BIGINT NOT NULL, currency VARCHAR(3) NOT NULL DEFAULT 'EUR', status VARCHAR(50) NOT NULL DEFAULT 'pending', error_message TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_seller_transfers_seller ON seller_transfers(seller_id); CREATE INDEX idx_seller_transfers_order ON seller_transfers(order_id); CREATE UNIQUE INDEX idx_seller_transfers_seller_order ON seller_transfers(seller_id, order_id); ``` ### 5.3 Injection dans marketplace.Service Le `StripeConnectService` sera injecté via le pattern `ServiceOption` existant : ```go // TransferService abstraction pour découpler marketplace du service Stripe type TransferService interface { CreateTransfer(ctx context.Context, sellerUserID uuid.UUID, amount int64, currency, orderID string) error } // WithTransferService injecte le service de transfert func WithTransferService(ts TransferService, feeRate float64) ServiceOption { return func(s *Service) { s.transferService = ts s.platformFeeRate = feeRate } } ``` ### 5.4 Logique transfer dans ProcessPaymentWebhook Après la boucle de création des licences (dans le cas `succeeded`), ajouter : 1. Regrouper les items par `product.SellerID` 2. Pour chaque vendeur unique : a. Calculer le montant total des items de ce vendeur b. Soustraire la commission plateforme c. Vérifier si le vendeur a un compte Connect actif (via DB) d. Si oui : appeler `CreateTransfer`, enregistrer un `SellerTransfer` avec status `completed` e. Si non : logger un warning, enregistrer un `SellerTransfer` avec status `skipped` f. En cas d'erreur Stripe : enregistrer avec status `failed` + `error_message`, ne pas bloquer la commande ### 5.5 Gestion des erreurs - Un échec de transfert **ne doit pas** annuler la commande ni les licences - Le transfert est enregistré en DB avec son statut pour retry ultérieur - Log `zap.Error` pour alerter l'équipe --- ## 6. Fichiers impactés (récapitulatif) ### Backend Go (nouveau) | Fichier | Action | |---------|--------| | `migrations/115_seller_transfers.sql` | Nouveau — table suivi transferts | ### Backend Go (modifier) | Fichier | Action | |---------|--------| | `internal/config/config.go` | Ajout `PlatformFeeRate` | | `internal/core/marketplace/models.go` | Ajout modèle `SellerTransfer` | | `internal/core/marketplace/service.go` | Interface `TransferService`, option `WithTransferService`, logique transfer dans `ProcessPaymentWebhook` | | `internal/core/marketplace/process_webhook_test.go` | Tests transfer, multi-vendeur, vendeur sans Connect | | `internal/handlers/sell_handler.go` | GET /sell/transfers | | `.env.example` | Documenter `PLATFORM_FEE_RATE` | ### Frontend (modifier) | Fichier | Action | |---------|--------| | `apps/web/src/features/seller/SellerDashboardView.tsx` | Carte transferts | | `apps/web/src/mocks/handlers.ts` | Handler GET /sell/transfers | ### Documentation | Fichier | Action | |---------|--------| | `docs/PAYOUT_MANUAL.md` | Mise à jour (procédure auto) | | `docs/PROJECT_STATE.md` | Section v0.603 | | `docs/FEATURE_STATUS.md` | Marketplace → transfert auto | | `CHANGELOG.md` | Section v0.603 | | `docs/SMOKE_TEST_V0603.md` | Nouveau | | `docs/RETROSPECTIVE_V0603.md` | Nouveau (en fin de release) | --- ## 7. Critères d'acceptation globaux - [ ] Transfer auto : paiement réussi (webhook) → licences créées + transfer Stripe exécuté - [ ] Multi-vendeur : commande multi-produit → 1 transfer par vendeur distinct - [ ] Commission : montant vendeur = prix - commission plateforme (configurable) - [ ] Résilience : échec transfer ≠ échec commande — enregistré avec status `failed` - [ ] Vendeur sans Connect : transfer ignoré (status `skipped`), log warning - [ ] GET /sell/transfers : vendeur voit historique de ses transferts - [ ] Frontend : carte transferts dans SellerDashboard - [ ] Tests : unitaires ProcessWebhook + transfer, multi-vendeur, sans Connect - [ ] TODOs : 50%+ des TODOs obsolètes supprimés, restants en issues - [ ] Tag v0.603 créé --- ## 8. Risques | Risque | Mitigation | |--------|------------| | Stripe Connect rate limits (250 req/s) | Batch par commande, pas par item. En prod, volume faible au démarrage | | Erreur transfer après licences créées | Transfer enregistré `failed` en DB — retry manuel ou cron v0.604 | | Float precision sur montants | Calcul en centimes (int64) uniquement, conversion float → int64 dès l'entrée | | Commission non configurable par vendeur | MVP = taux unique global. Taux par vendeur/catégorie → v0.604 | --- ## 9. Références - [RETROSPECTIVE_V0602.md](RETROSPECTIVE_V0602.md) - [PAYOUT_MANUAL.md](PAYOUT_MANUAL.md) - [SCOPE_CONTROL.md](SCOPE_CONTROL.md) - [Stripe Connect Transfers](https://stripe.com/docs/connect/charges#transfer-availability) - `veza-backend-api/internal/services/stripe_connect_service.go` - `veza-backend-api/internal/core/marketplace/service.go`