Define v0.603 release scope: automatic Stripe Connect transfers after payment, configurable platform commission, technical debt triage (210+ TODOs), and docs archival. Includes detailed implementation plan (4 sprints, 19 commits) and smoke test checklist.
619 lines
20 KiB
Markdown
619 lines
20 KiB
Markdown
# Plan d'implémentation v0.603 — Transfer automatique, Commission & Stabilisation
|
|
|
|
**Date** : 2026-02-23
|
|
**Base** : v0.602 taguée
|
|
**Durée estimée** : 4 sprints (~20 jours ouvrés)
|
|
**Référence** : [V0_603_RELEASE_SCOPE.md](V0_603_RELEASE_SCOPE.md)
|
|
|
|
---
|
|
|
|
## Vue d'ensemble
|
|
|
|
```
|
|
Sprint 1 (j1-5) → T1-01..T1-05 : Config, migration, modèle, interface, injection
|
|
Sprint 2 (j6-12) → T1-06..T1-10 : Logique transfer, endpoint, frontend, tests
|
|
Sprint 3 (j13-17) → DT1 : Triage TODOs, archivage docs, nettoyage
|
|
Sprint 4 (j18-20) → QA3 : Tests E2E, smoke test, docs, rétro, tag
|
|
```
|
|
|
|
---
|
|
|
|
## Diagramme d'architecture cible
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
subgraph Webhook["Hyperswitch Webhook (payment.succeeded)"]
|
|
PW["ProcessPaymentWebhook"]
|
|
end
|
|
|
|
subgraph Transfer["Transfer Flow (nouveau v0.603)"]
|
|
Group["Regrouper items par seller_id"]
|
|
Calc["Calculer montant net = prix - commission"]
|
|
Check["Vendeur a un compte Connect ?"]
|
|
Yes["CreateTransfer (Stripe API)"]
|
|
No["Log warning, status = skipped"]
|
|
Record["Enregistrer SellerTransfer en DB"]
|
|
end
|
|
|
|
subgraph Existing["Existant (v0.602)"]
|
|
License["Créer licences"]
|
|
Order["Order status = completed"]
|
|
end
|
|
|
|
PW --> Order
|
|
PW --> License
|
|
License --> Group
|
|
Group --> Calc
|
|
Calc --> Check
|
|
Check -->|Oui| Yes
|
|
Check -->|Non| No
|
|
Yes --> Record
|
|
No --> Record
|
|
```
|
|
|
|
---
|
|
|
|
## Sprint 1 — Config, migration, modèle, injection (jours 1-5)
|
|
|
|
> **Objectif** : Préparer l'infrastructure backend pour les transferts.
|
|
|
|
### Tâche T1-01 : Config commission plateforme
|
|
|
|
**Fichier** : `veza-backend-api/internal/config/config.go`
|
|
|
|
Ajouter dans la struct Config :
|
|
|
|
```go
|
|
PlatformFeeRate float64 // PLATFORM_FEE_RATE, default 0.10 (10%)
|
|
```
|
|
|
|
Chargement dans `LoadConfig()` :
|
|
|
|
```go
|
|
cfg.PlatformFeeRate = getEnvFloat("PLATFORM_FEE_RATE", 0.10)
|
|
```
|
|
|
|
**Fichier** : `.env.example`
|
|
|
|
```env
|
|
# Platform commission on marketplace sales (0.10 = 10%)
|
|
PLATFORM_FEE_RATE=0.10
|
|
```
|
|
|
|
**Commit** : `feat(commerce): add PLATFORM_FEE_RATE config (default 10%)`
|
|
|
|
---
|
|
|
|
### Tâche T1-02 : Migration seller_transfers
|
|
|
|
**Fichier** : `veza-backend-api/migrations/115_seller_transfers.sql`
|
|
|
|
```sql
|
|
-- v0.603 T1-02: Seller transfer tracking
|
|
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 IF NOT EXISTS idx_seller_transfers_seller ON seller_transfers(seller_id);
|
|
CREATE INDEX IF NOT EXISTS idx_seller_transfers_order ON seller_transfers(order_id);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_seller_transfers_seller_order
|
|
ON seller_transfers(seller_id, order_id);
|
|
```
|
|
|
|
**Commit** : `feat(commerce): add 115_seller_transfers migration`
|
|
|
|
---
|
|
|
|
### Tâche T1-03 : Modèle SellerTransfer
|
|
|
|
**Fichier** : `veza-backend-api/internal/core/marketplace/models.go`
|
|
|
|
Ajouter après le type `ProductReview` :
|
|
|
|
```go
|
|
// SellerTransfer tracks a Stripe Connect transfer for a completed order.
|
|
type SellerTransfer struct {
|
|
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
|
|
SellerID uuid.UUID `gorm:"type:uuid;not null" json:"seller_id"`
|
|
OrderID uuid.UUID `gorm:"type:uuid;not null" json:"order_id"`
|
|
StripeTransferID string `gorm:"size:255" json:"stripe_transfer_id,omitempty"`
|
|
AmountCents int64 `gorm:"not null" json:"amount_cents"`
|
|
PlatformFeeCents int64 `gorm:"not null" json:"platform_fee_cents"`
|
|
Currency string `gorm:"size:3;default:'EUR'" json:"currency"`
|
|
Status string `gorm:"size:50;default:'pending'" json:"status"` // pending, completed, failed, skipped
|
|
ErrorMessage string `gorm:"type:text" json:"error_message,omitempty"`
|
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
|
}
|
|
|
|
func (st *SellerTransfer) BeforeCreate(tx *gorm.DB) (err error) {
|
|
if st.ID == uuid.Nil {
|
|
st.ID = uuid.New()
|
|
}
|
|
return
|
|
}
|
|
```
|
|
|
|
**Commit** : `feat(commerce): add SellerTransfer model`
|
|
|
|
---
|
|
|
|
### Tâche T1-04 : Interface TransferService
|
|
|
|
**Fichier** : `veza-backend-api/internal/core/marketplace/service.go`
|
|
|
|
Ajouter avant le type `Service struct` :
|
|
|
|
```go
|
|
// TransferService abstracts the payout transfer provider.
|
|
type TransferService interface {
|
|
CreateTransfer(ctx context.Context, sellerUserID uuid.UUID, amount int64, currency, orderID string) error
|
|
}
|
|
```
|
|
|
|
**Commit** : combiné avec T1-05
|
|
|
|
---
|
|
|
|
### Tâche T1-05 : Option WithTransferService
|
|
|
|
**Fichier** : `veza-backend-api/internal/core/marketplace/service.go`
|
|
|
|
Ajouter les champs dans `Service struct` :
|
|
|
|
```go
|
|
type Service struct {
|
|
db *gorm.DB
|
|
logger *zap.Logger
|
|
storage StorageService
|
|
paymentProvider PaymentProvider
|
|
hyperswitchEnabled bool
|
|
checkoutSuccessURL string
|
|
transferService TransferService // v0.603: optional payout transfer
|
|
platformFeeRate float64 // v0.603: platform commission rate (e.g. 0.10)
|
|
}
|
|
```
|
|
|
|
Ajouter la `ServiceOption` :
|
|
|
|
```go
|
|
// WithTransferService injects the payout transfer service and platform fee rate.
|
|
func WithTransferService(ts TransferService, feeRate float64) ServiceOption {
|
|
return func(s *Service) {
|
|
s.transferService = ts
|
|
s.platformFeeRate = feeRate
|
|
}
|
|
}
|
|
```
|
|
|
|
**Fichier** : Wiring — là où `marketplace.NewService` est appelé (probablement `cmd/server/main.go` ou `internal/api/setup.go`), ajouter l'option :
|
|
|
|
```go
|
|
marketplace.WithTransferService(stripeConnectService, cfg.PlatformFeeRate)
|
|
```
|
|
|
|
**Commit** : `feat(commerce): add TransferService interface and WithTransferService option`
|
|
|
|
---
|
|
|
|
## Sprint 2 — Logique transfer, endpoint, frontend (jours 6-12)
|
|
|
|
> **Objectif** : Implémenter le flux transfer dans le webhook et exposer l'historique.
|
|
|
|
### Tâche T1-06 : Logique transfer dans ProcessPaymentWebhook
|
|
|
|
**Fichier** : `veza-backend-api/internal/core/marketplace/service.go`
|
|
|
|
Dans la méthode `ProcessPaymentWebhook`, dans le case `"succeeded"`, **après** la boucle de création des licences, ajouter :
|
|
|
|
```go
|
|
// v0.603 T1-06: Transfer to sellers after license creation
|
|
if s.transferService != nil {
|
|
s.processSellerTransfers(ctx, tx, &order, items)
|
|
}
|
|
```
|
|
|
|
Nouvelle méthode privée `processSellerTransfers` :
|
|
|
|
```go
|
|
// processSellerTransfers groups order items by seller and initiates transfers.
|
|
// Errors are logged and recorded but do not fail the order.
|
|
func (s *Service) processSellerTransfers(ctx context.Context, tx *gorm.DB, order *Order, items []OrderItem) {
|
|
// 1. Group items by seller, sum prices
|
|
sellerTotals := make(map[uuid.UUID]float64)
|
|
for _, item := range items {
|
|
var product Product
|
|
if err := tx.First(&product, "id = ?", item.ProductID).Error; err != nil {
|
|
s.logger.Warn("Transfer: product not found for item",
|
|
zap.String("item_id", item.ID.String()))
|
|
continue
|
|
}
|
|
sellerTotals[product.SellerID] += item.Price
|
|
}
|
|
|
|
// 2. For each seller, calculate net amount and transfer
|
|
for sellerID, totalPrice := range sellerTotals {
|
|
grossCents := int64(totalPrice * 100)
|
|
feeCents := int64(float64(grossCents) * s.platformFeeRate)
|
|
netCents := grossCents - feeCents
|
|
|
|
st := SellerTransfer{
|
|
SellerID: sellerID,
|
|
OrderID: order.ID,
|
|
AmountCents: netCents,
|
|
PlatformFeeCents: feeCents,
|
|
Currency: order.Currency,
|
|
Status: "pending",
|
|
}
|
|
|
|
err := s.transferService.CreateTransfer(ctx, sellerID, netCents, order.Currency, order.ID.String())
|
|
if err != nil {
|
|
st.Status = "failed"
|
|
st.ErrorMessage = err.Error()
|
|
s.logger.Error("Transfer failed for seller",
|
|
zap.String("seller_id", sellerID.String()),
|
|
zap.String("order_id", order.ID.String()),
|
|
zap.Error(err))
|
|
} else {
|
|
st.Status = "completed"
|
|
s.logger.Info("Transfer completed for seller",
|
|
zap.String("seller_id", sellerID.String()),
|
|
zap.Int64("amount_cents", netCents))
|
|
}
|
|
|
|
if err := tx.Create(&st).Error; err != nil {
|
|
s.logger.Error("Failed to record seller transfer",
|
|
zap.String("seller_id", sellerID.String()),
|
|
zap.Error(err))
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Commit** : `feat(commerce): trigger seller transfers on payment succeeded`
|
|
|
|
---
|
|
|
|
### Tâche T1-07 : Endpoint GET /sell/transfers
|
|
|
|
**Fichier** : `veza-backend-api/internal/handlers/sell_handler.go`
|
|
|
|
Ajouter le handler `GetSellerTransfers` :
|
|
|
|
```go
|
|
// GetSellerTransfers returns the transfer history for the authenticated seller.
|
|
// GET /api/v1/sell/transfers
|
|
func (h *SellHandler) GetSellerTransfers(c *gin.Context) {
|
|
userID := getUserID(c)
|
|
var transfers []marketplace.SellerTransfer
|
|
if err := h.db.Where("seller_id = ?", userID).
|
|
Order("created_at DESC").
|
|
Find(&transfers).Error; err != nil {
|
|
c.JSON(500, gin.H{"error": "failed to fetch transfers"})
|
|
return
|
|
}
|
|
c.JSON(200, gin.H{"data": transfers})
|
|
}
|
|
```
|
|
|
|
**Fichier** : routes — ajouter `GET /sell/transfers` dans le groupe sell authentifié.
|
|
|
|
**Commit** : `feat(commerce): add GET /sell/transfers endpoint`
|
|
|
|
---
|
|
|
|
### Tâche T1-08 : Frontend — SellerTransfersCard
|
|
|
|
**Fichier** : `apps/web/src/features/seller/SellerDashboardView.tsx`
|
|
|
|
Ajouter une carte « Historique des transferts » dans le dashboard vendeur :
|
|
- Appel `GET /sell/transfers`
|
|
- Liste : date, montant, commission, statut (badge coloré)
|
|
- États : Loading (skeleton), Empty (« Aucun transfert »), Error
|
|
|
|
**Fichier** : `apps/web/src/services/marketplaceService.ts`
|
|
|
|
```typescript
|
|
export const getSellerTransfers = () =>
|
|
apiClient.get<{ data: SellerTransfer[] }>('/sell/transfers');
|
|
|
|
export interface SellerTransfer {
|
|
id: string;
|
|
order_id: string;
|
|
amount_cents: number;
|
|
platform_fee_cents: number;
|
|
currency: string;
|
|
status: 'pending' | 'completed' | 'failed' | 'skipped';
|
|
error_message?: string;
|
|
created_at: string;
|
|
}
|
|
```
|
|
|
|
**Commit** : `feat(seller): add transfers history card to SellerDashboard`
|
|
|
|
---
|
|
|
|
### Tâche T1-09 : MSW handlers + Story
|
|
|
|
**Fichier** : `apps/web/src/mocks/handlers.ts`
|
|
|
|
```typescript
|
|
http.get('/api/v1/sell/transfers', () => {
|
|
return HttpResponse.json({
|
|
data: [
|
|
{
|
|
id: '...',
|
|
order_id: '...',
|
|
amount_cents: 900,
|
|
platform_fee_cents: 100,
|
|
currency: 'EUR',
|
|
status: 'completed',
|
|
created_at: '2026-02-23T10:00:00Z',
|
|
},
|
|
],
|
|
});
|
|
}),
|
|
```
|
|
|
|
**Story** : `SellerDashboardView.stories.tsx` — ajouter un état « Avec transferts » montrant la carte remplie.
|
|
|
|
**Commit** : `test(seller): add MSW handler and story for transfers`
|
|
|
|
---
|
|
|
|
### Tâche T1-10 : Tests unitaires transfer
|
|
|
|
**Fichier** : `veza-backend-api/internal/core/marketplace/process_webhook_test.go`
|
|
|
|
Ajouter 3 cas de test :
|
|
|
|
1. **TestProcessWebhook_TransferSuccess** — paiement réussi → license créée + transfer `completed` enregistré
|
|
2. **TestProcessWebhook_MultiSeller** — 2 items de 2 vendeurs → 2 `SellerTransfer` distincts
|
|
3. **TestProcessWebhook_SellerNoConnect** — vendeur sans compte Connect → `CreateTransfer` retourne `ErrNoStripeAccount` → transfer `failed` enregistré, commande quand même `completed`
|
|
|
|
Mock `TransferService` :
|
|
|
|
```go
|
|
type mockTransferService struct {
|
|
callCount int
|
|
err error
|
|
}
|
|
|
|
func (m *mockTransferService) CreateTransfer(ctx context.Context, sellerUserID uuid.UUID, amount int64, currency, orderID string) error {
|
|
m.callCount++
|
|
return m.err
|
|
}
|
|
```
|
|
|
|
**Commit** : `test(commerce): add transfer tests — success, multi-seller, no-connect`
|
|
|
|
---
|
|
|
|
## Sprint 3 — Dette technique & Nettoyage (jours 13-17)
|
|
|
|
> **Objectif** : Triage des TODOs, archivage des docs obsolètes.
|
|
|
|
### Tâche DT1-01 : Triage TODOs backend
|
|
|
|
**Procédure** :
|
|
|
|
```bash
|
|
cd veza-backend-api
|
|
rg -n "TODO|FIXME" --type go | sort > /tmp/todos.txt
|
|
wc -l /tmp/todos.txt # ~210
|
|
```
|
|
|
|
Pour chaque TODO :
|
|
- **Obsolète** (code déjà refactoré, feature livrée) → supprimer le commentaire
|
|
- **Pertinent** (bug connu, amélioration future) → créer une issue GitHub, garder un `// TODO(#NNN): ...`
|
|
- **Objectif** : réduire de 210 à < 100
|
|
|
|
**Commit** : `chore(backend): triage TODOs — remove 100+ obsolete, convert rest to issues`
|
|
|
|
---
|
|
|
|
### Tâche DT1-02 : Archiver docs obsolètes
|
|
|
|
**Déplacer vers** `docs/archive/` les docs pré-v0.501 qui ne sont plus des références actives :
|
|
|
|
- `V0_101_*.md`, `V0_102_*.md`, `V0_103_*.md`
|
|
- `V0_201_*.md`, `V0_202_*.md`, `V0_203_*.md`
|
|
- `V0_301_*.md`, `V0_302_*.md`, `V0_303_*.md`
|
|
- `V0_401_*.md`, `V0_402_*.md`, `V0_403_*.md`
|
|
- `PLAN_V0_301_*.md`, `PLAN_V0_401_*.md`, `PLAN_V0_402_*.md`, `PLAN_V0_403_*.md`
|
|
- Anciens audits : `AUDIT_TEMP_*.md`, `DB_MIGRATIONS_AUDIT_*.md`, `UUID_DB_*.md`, `AUDIT_DB_*.md`
|
|
|
|
**Ne pas archiver** : `SCOPE_CONTROL.md`, `PROJECT_STATE.md`, `FEATURE_STATUS.md`, `MIGRATIONS.md`, `ENV_CONFIG.md`, `ONBOARDING.md`, `MONITORING_SETUP.md`, `STORYBOOK_CONTRACT.md`, plans v0.601+.
|
|
|
|
**Commit** : `chore(docs): archive obsolete pre-v0.501 docs`
|
|
|
|
---
|
|
|
|
### Tâche DT1-03 : Mettre à jour PAYOUT_MANUAL.md
|
|
|
|
**Fichier** : `docs/PAYOUT_MANUAL.md`
|
|
|
|
Remplacer la section « Procédure manuelle » par :
|
|
|
|
```markdown
|
|
## Implémentation v0.603
|
|
|
|
Le transfert automatique est opérationnel depuis v0.603 :
|
|
1. Paiement réussi (webhook Hyperswitch `succeeded`)
|
|
2. Licences créées pour l'acheteur
|
|
3. Items regroupés par vendeur
|
|
4. Commission plateforme déduite (PLATFORM_FEE_RATE)
|
|
5. Transfer Stripe Connect exécuté vers le compte du vendeur
|
|
6. SellerTransfer enregistré en DB (table seller_transfers)
|
|
|
|
Voir : V0_603_RELEASE_SCOPE.md § 5 pour le détail technique.
|
|
```
|
|
|
|
**Commit** : `docs(payout): update PAYOUT_MANUAL for v0.603 auto transfer`
|
|
|
|
---
|
|
|
|
### Tâche DT1-04 : Nettoyage code mort marketplace
|
|
|
|
**Fichier** : `veza-backend-api/internal/core/marketplace/`
|
|
|
|
- Vérifier imports inutilisés
|
|
- Vérifier variables déclarées non utilisées
|
|
- Lancer `go vet ./internal/core/marketplace/...`
|
|
|
|
**Commit** : `chore(marketplace): remove dead code and unused imports`
|
|
|
|
---
|
|
|
|
## Sprint 4 — Tests E2E, docs, tag (jours 18-20)
|
|
|
|
> **Objectif** : Validation complète, documentation, release.
|
|
|
|
### Tâche QA3-01 : Test E2E transfer
|
|
|
|
**Fichier** : `veza-backend-api/internal/core/marketplace/process_webhook_test.go` ou nouveau fichier d'intégration.
|
|
|
|
Test complet dans une transaction :
|
|
1. Créer un user vendeur avec `seller_stripe_accounts` (PayoutsEnabled = true)
|
|
2. Créer un produit de ce vendeur
|
|
3. Créer une commande (buyer, 1 item)
|
|
4. Simuler webhook `succeeded` avec `ProcessPaymentWebhook`
|
|
5. Vérifier : order `completed`, license créée, `SellerTransfer` enregistré avec `status = completed`
|
|
|
|
**Commit** : `test(commerce): add E2E transfer flow test`
|
|
|
|
---
|
|
|
|
### Tâche QA3-02 : Test multi-vendeur
|
|
|
|
Même setup que QA3-01 mais :
|
|
- 2 vendeurs avec comptes Connect
|
|
- 1 commande avec 2 items (1 par vendeur)
|
|
- Vérifier : 2 `SellerTransfer` distincts, montants corrects
|
|
|
|
**Commit** : combiné avec QA3-01
|
|
|
|
---
|
|
|
|
### Tâche QA3-03 : Test vendeur sans Connect
|
|
|
|
- 1 vendeur **sans** entrée dans `seller_stripe_accounts`
|
|
- 1 commande, webhook `succeeded`
|
|
- Vérifier : licences créées, `SellerTransfer` avec `status = failed`, commande `completed`
|
|
|
|
**Commit** : combiné avec QA3-01
|
|
|
|
---
|
|
|
|
### Tâche QA3-04 : Smoke test v0.603
|
|
|
|
**Fichier** : `docs/SMOKE_TEST_V0603.md` — voir document dédié.
|
|
|
|
**Commit** : `docs: add SMOKE_TEST_V0603.md`
|
|
|
|
---
|
|
|
|
### Tâche QA3-05 : Mise à jour documentation
|
|
|
|
**Fichiers** :
|
|
- `docs/PROJECT_STATE.md` — ajouter section v0.603 livrée
|
|
- `docs/FEATURE_STATUS.md` — Marketplace → « Transfer automatique opérationnel (v0.603) »
|
|
- `CHANGELOG.md` — section v0.603
|
|
|
|
**Commit** : `docs: update PROJECT_STATE, FEATURE_STATUS, CHANGELOG for v0.603`
|
|
|
|
---
|
|
|
|
### Tâche QA3-06 : Rétrospective, archivage, tag
|
|
|
|
1. Créer `docs/RETROSPECTIVE_V0603.md`
|
|
2. Déplacer `docs/V0_603_RELEASE_SCOPE.md` → `docs/archive/`
|
|
3. Créer placeholder `docs/V0_604_RELEASE_SCOPE.md`
|
|
4. Mettre à jour `docs/SCOPE_CONTROL.md` — référence active → V0_604
|
|
5. Tag : `git tag -a v0.603 -m "v0.603 — Transfer automatique, Commission & Stabilisation"`
|
|
|
|
**Commits** :
|
|
- `docs: add RETROSPECTIVE_V0603.md`
|
|
- `chore(release): archive v0.603 scope, create v0.604 placeholder`
|
|
- `chore(release): v0.603 — Transfer automatique, Commission & Stabilisation`
|
|
|
|
---
|
|
|
|
## Commits récapitulatifs (ordre d'exécution)
|
|
|
|
| # | Sprint | Commit |
|
|
|---|--------|--------|
|
|
| 1 | 1 | `feat(commerce): add PLATFORM_FEE_RATE config (default 10%)` |
|
|
| 2 | 1 | `feat(commerce): add 115_seller_transfers migration` |
|
|
| 3 | 1 | `feat(commerce): add SellerTransfer model` |
|
|
| 4 | 1 | `feat(commerce): add TransferService interface and WithTransferService option` |
|
|
| 5 | 2 | `feat(commerce): trigger seller transfers on payment succeeded` |
|
|
| 6 | 2 | `feat(commerce): add GET /sell/transfers endpoint` |
|
|
| 7 | 2 | `feat(seller): add transfers history card to SellerDashboard` |
|
|
| 8 | 2 | `test(seller): add MSW handler and story for transfers` |
|
|
| 9 | 2 | `test(commerce): add transfer tests — success, multi-seller, no-connect` |
|
|
| 10 | 3 | `chore(backend): triage TODOs — remove 100+ obsolete, convert rest to issues` |
|
|
| 11 | 3 | `chore(docs): archive obsolete pre-v0.501 docs` |
|
|
| 12 | 3 | `docs(payout): update PAYOUT_MANUAL for v0.603 auto transfer` |
|
|
| 13 | 3 | `chore(marketplace): remove dead code and unused imports` |
|
|
| 14 | 4 | `test(commerce): add E2E transfer flow test` |
|
|
| 15 | 4 | `docs: add SMOKE_TEST_V0603.md` |
|
|
| 16 | 4 | `docs: update PROJECT_STATE, FEATURE_STATUS, CHANGELOG for v0.603` |
|
|
| 17 | 4 | `docs: add RETROSPECTIVE_V0603.md` |
|
|
| 18 | 4 | `chore(release): archive v0.603 scope, create v0.604 placeholder` |
|
|
| 19 | 4 | `chore(release): v0.603 — Transfer automatique, Commission & Stabilisation` |
|
|
|
|
---
|
|
|
|
## Dépendances entre lots
|
|
|
|
```
|
|
T1-01..T1-05 (Config/Migration/Modèle) → aucune dépendance
|
|
T1-06 (Logique transfer) → dépend de T1-01..T1-05
|
|
T1-07 (Endpoint) → dépend de T1-03
|
|
T1-08..T1-09 (Frontend) → dépend de T1-07
|
|
T1-10 (Tests unitaires) → dépend de T1-06
|
|
DT1 (Dette technique) → indépendant (peut démarrer en parallèle)
|
|
QA3 (Tests E2E, docs) → dépend de T1, DT1
|
|
```
|
|
|
|
---
|
|
|
|
## Risques et mitigations
|
|
|
|
| Risque | Mitigation |
|
|
|--------|------------|
|
|
| Stripe rate limit sur CreateTransfer | 1 transfer par vendeur par commande, pas par item |
|
|
| Échec transfer silencieux | Enregistré en DB, log `zap.Error`, alertable via Grafana/Alertmanager |
|
|
| Float→int64 precision loss | Conversion `int64(price * 100)` au plus tôt, calculs en centimes |
|
|
| TODO triage trop long | Timebox à 2 jours max, focus sur `internal/core/` et `internal/handlers/` |
|
|
| Migration 115 conflict | Vérifier dernier numéro avant de commencer |
|
|
|
|
---
|
|
|
|
## Validation finale
|
|
|
|
```bash
|
|
# Backend
|
|
cd veza-backend-api && go build ./...
|
|
cd veza-backend-api && go test ./... -v
|
|
cd veza-backend-api && go test ./internal/core/marketplace/... -v -run TestProcessWebhook
|
|
|
|
# Frontend
|
|
cd apps/web && npm run build
|
|
cd apps/web && npm test -- --run
|
|
|
|
# Storybook
|
|
cd apps/web && npm run build-storybook
|
|
cd apps/web && npx http-server storybook-static -p 6007 &
|
|
cd apps/web && npm run test:storybook
|
|
```
|