238 lines
11 KiB
Markdown
238 lines
11 KiB
Markdown
# 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`
|