Marketplace — Roadmap technique détaillée
Pendant éditorial de marketplace.md. Trace ce qui est livré, ce qui manque, et l'ordre d'attaque. Mis à jour : 2026-09.
Pendant éditorial de
marketplace.md. Trace ce qui est livré, ce qui manque, et l'ordre d'attaque. Mis à jour : 2026-09.
✅ V1 (déjà en prod)
- Prisma schema complet :
MarketplaceListing,VerifiedRevenue,Offer,DealThread,DealMessage,Order,Review,SavedListing. (BuyerProfilefigurait ici : il a été retiré du schéma en juin 2026, cf.marketplace.mdsection 3.) - 11 API publiques + 2 admin.
- Queue moderation admin (Upstash Redis zset + hash) + decision form.
- Routes publiques
/marketplace,/marketplace/[type],/marketplace/[type]/[slug],/marketplace/hire. - Composants shared (19 fichiers).
- Email Resend offer OTP + offer received + lead.
- Sitemap + robots + JSON-LD basique.
- Stripe Checkout one-time + Stripe Tax + 3% fee.
- Privacy mode (anonymous listings).
- Token de preview privé pour partager paused listings.
✅ V1.1 (cette PR)
Schema Prisma
- Indexes composites
(type, status, publishedAt DESC)x2. - Indexes composites
(sellerId, status, publishedAt DESC). - Indexes composites DealThread
(sellerId, status, createdAt DESC)+ buyer. - Index Review
(listingId, createdAt DESC)+(listingId, verifiedBuyer, createdAt DESC). - Index Order
(buyerId, status, createdAt DESC)+(listingId, status). - Index VerifiedRevenue
(source, refreshFailedAt). - Unique constraint Offer
(listingId, buyerEmail, status): anti-doublon. - Index Offer
(expiresAt, status)pour cron expiration.
Constants
src/types/marketplace-constants.ts— source unique pourTYPE_TO_PLURAL,PLURAL_TO_TYPE,LOWER_TO_TYPE,TYPE_TO_LOWER,TYPE_COPY,TYPE_ORDER,BUYABLE_TYPES,HIRE_TYPES,APPLY_TYPES,REVIEWABLE_TYPES,contactIntentFor(),categoryPathFor(),listingPathFor(),resolveTypeFromPlural(),resolveTypeFromLower().- Pages
/marketplace,/marketplace/[type]migrées vers ces constants.
Sécurité & idempotence
- Rate limit
/api/marketplace/save: 50/min/user. - Rate limit
/api/marketplace/recommendations: 30/min/IP + cursor pagination. - Rate limit routes admin POST/PATCH/DELETE (10-50/min/admin).
AdminAuditLogsur PATCH (avec diff before/after) + DELETE.- Checkout idempotent : réutilise Order PENDING + Stripe
idempotencyKey. - Offers : dedup
findFirstpre-check + catch P2002 → 409. - Reviews : upsert + agg refresh dans une
$transaction. - Save :
rateLimit+ 404 explicite si listing introuvable. - Leads : dedup hash SHA-256 60s + Resend fail loggé structuré.
- Slug uniqueness admin : catch P2002 + retry suffixe au lieu de boucle 1000× findUnique.
Frontend
force-dynamic→revalidate(1h index, 5min type+detail).error.tsx+loading.tsxsur/marketplace.- Empty state CTA enrichi.
- Pagination
rel="prev"/rel="next"pour le crawl ranking. og:imagemasqué en mode anonyme.
SEO
productJsonLdenrichi avec :offers(price + priceCurrency + priceLabel) si prix ONE_TIME/SUBSCRIPTION.aggregateRatingsi reviewsCount > 0.
Service unified
- Cache Upstash Redis 5min sur
listUnified()(clé anonyme, viewer flags joints au runtime). - Fusion
findUnifiedBySlug+loadLegacyDetaildans le service : retourne{ card, source, legacy }. - Structured logging sur fallback DB→JSON (monitoring migration).
trackListingView: catch + log silencieux.
Notifications
- Template email
marketplace-decision.tsx(approved/rejected). - Service
marketplace-decisions.ts. - Wired dans
decideDraft(queue admin) : best-effort, n'échoue jamais. - Resolve submitter email : User.email → details.email → noop.
Vendor dashboard skeleton
/[orgSlug]/~/listings— index + KPI rail + table./[orgSlug]/~/listings/[id]— detail + offres en attente + deals.
Docs
docs/architecture/marketplace.md— référence architecture.docs/architecture/marketplace-roadmap.md, ce fichier.CLAUDE.mdmis à jour avec nouveaux endpoints + routes vendor.- Seed marketplace 100% automatique au cold-start (sync-from-content):
plus de script manuel
db:seed:marketplace.
✅ V1.2 — Vendor edit + search + audit filters (livré cette PR)
- ✅ Vendor edit form
/[orgSlug]/~/listings/[id]/editcomplet : rhf + zod + 3 sections (Identity / Pricing / Privacy) + delete dialog. - ✅ API vendor CRUD
/api/vendor/marketplace/listings(GET/POST/PATCH/DELETE)- offer actions accept/reject.
- ✅ Subscription pricing model supporté en checkout (
mode: subscription, monthly interval). - ✅ Postgres
tsvectorfull-text search viaservices/marketplace/search.ts- API
/api/marketplace/search. Pondération A/B/C/D, cache Redis 5min.
- API
- ✅ Admin audit log filtres : actor / action / resource + cursor pagination.
✅ V1.3 — Deal management + queue filters + markdown preview + search UI + analytics (livré cette PR)
- ✅ Deal management vendor :
/[orgSlug]/~/dealsindex +/[orgSlug]/~/deals/[id]detail.- Pipeline visualizer 7 stages + asset checklist + messages chat.
- Stage actions (advance + sign nda/loi/apa) seller-only.
- API : POST
/api/vendor/marketplace/deals/[id]/messages|advance|sign.
- ✅ Admin queue filters : type / boost / q via GET-form + cap 200 drafts loaded (vs 50 avant) pour absorber les filtres.
- ✅ Bulk reject server action + UI sticky bar.
- ✅ Markdown editor avec live preview toggle (rendu client-side via react-markdown + remark-gfm).
- ✅ Public marketplace search UI : autocomplete debounced 250ms,
keyboard nav (↑↓/Enter/Esc/), keyboard shortcut
/, hits MarketplaceSearch. - ✅ Vendor analytics page
/[orgSlug]/~/listings/analytics: totals all-time + KPIs 30j (offers / deals / orders / revenue / fee)- top 5 listings par saves.
✅ V2 — Cancel deal + buyer dashboard + screenshots + privacy preview UI (livré cette PR)
- ✅ Cancel deal action (service + API + UI dialog avec warning escrow).
- ✅ Buyer dashboard E2E :
/account/ordersindex avec status filter + total spent/account/offersindex avec status filter + dealThread link/account/dealsindex buyer-side +/account/deals/[id](non-org-scoped)
- ✅ Screenshots :
- Schema
MarketplaceListing.screenshots: String[](max 8). - ScreenshotsGallery component avec lightbox zoom.
- ScreenshotsEditor + Vercel Blob upload (kind="screenshot", 10MB).
- Wired dans vendor edit form + detail page (skip si anonyme).
- Schema
- ✅ Privacy preview token UI :
- PreviewTokenCard component (rotate / revoke / copy share URL).
- API
/api/vendor/marketplace/listings/[id]/preview-token. - Audit log per action.
- ✅ Dynamic OG images per listing :
/api/ogétendu (subtitle + icon params).buildMarketingMetadataogSubtitle + ogIcon props.- Detail page passe listing tagline + iconUrl au générateur OG (mieux que iconUrl raw comme og:image 1200×630).
⛔ V2.5 — In-app notifications + bell (retiré)
Schéma Notification, service services/notifications, routes
/api/notifications/*, composant NotificationsBell et SSE stream
ont été retirés au profit d'une stratégie 100 % email (Resend).
Les call sites in-app (notify(…)) ont été supprimés des services
marketplace ; seuls les emails Resend restent.
✅ V3 — Bulk actions + cancel email + FTS index (livré cette PR)
- ✅ Bulk vendor actions :
- POST
/api/vendor/marketplace/listings/bulk(pause / unpause / delete, max 50 IDs, owner-gated via filter sellerId). BulkActionsBarsticky-bottom +ListingsTableclient wrapper avec checkboxes Select all / per-row.
- POST
- ✅ Cancel deal email Resend :
- Template
marketplace-deal-cancelled.tsx(eyebrow + raison + warning special escrow funded). - Service
marketplace-deal-events.tssendDealCancelledEmail. - Hook :
cancelDealservice envoie l'email (best-effort).
- Template
- ✅ FTS index helper :
- Script
pnpm db:marketplace:fts-indexcrée GIN expression-index avec setweight A/B/C/D matching le servicesearch.ts. - Partial index supplémentaire WHERE status='LIVE' pour query principale.
- Idempotent (CREATE INDEX IF NOT EXISTS).
- À run quand volume dépasse ~10k listings.
- Script
✅ V4 — Stripe Connect + disputes + refund automation + timeline events (livré cette PR)
Stripe Connect (Express)
- ✅ Schema
StripeConnectAccount+StripeConnectStatusenum (PENDING / RESTRICTED / ENABLED / REJECTED). - ✅ Service
services/stripe/client.ts(centralized Stripe factory). - ✅ Service
services/stripe/connect.ts:createConnectAccount(idempotent, Express type, embeds boostecom_user_id metadata).createOnboardingLink(account_onboarding).createDashboardLink(Express Dashboard login).syncFromAccount(mirror flags + requirements + auto-notify ENABLED/RESTRICTED).transferToConnectAccount(Stripe Transfer, idempotencyKeytransfer:${orderId}).
- ✅ API : POST
/api/vendor/stripe/connect/onboard, GET/api/vendor/stripe/connect/status(with?refresh=1for live Stripe pull), POST/api/vendor/stripe/connect/dashboard. - ✅ Webhook :
account.updated→ syncFromAccount,payout.failed→ notify seller. - ✅ Payout vendeur en escrow : le transfert net (amount - fee) part du cron
marketplace-payoutsune fois la fenêtre de dispute de 14j fermée et aucune dispute ouverte, puis notifyMARKETPLACE_PAYOUT_SENT. Cette ligne a décrit un transfert immédiat depuishandleCheckoutCompleted, ce que la V4 a justement remplacé. - ✅ UI :
/account/settings/payouts(status card + onboarding CTA + refresh button + FAQ), Payouts tab added to/account/settingslayout.
Dispute resolution
- ✅ Schema
Dispute+DisputeStatus+DisputeReasonenums. - ✅ Service
services/marketplace/disputes.ts:raiseDispute(14-day window enforcement, anti-doublon @unique).sellerRespondToDispute(OPEN/AWAITING_SELLER → AWAITING_ADMIN).adminResolveDispute(REFUND_FULL/PARTIAL/NO_ACTION + auto-fire Stripe refund).withdrawDispute.autoEscalateStaleDisputes(cron hook).
- ✅ API : POST/GET
/api/marketplace/disputes, GET/PATCH/api/marketplace/disputes/[id], POST/api/vendor/marketplace/disputes/[id]/respond, POST/api/admin/marketplace/disputes/[id]/resolve(admin-audit logged). - ✅ Email :
MarketplaceDisputeEmailtemplate (3 events × 2 audiences) + serviceservices/email/marketplace-disputes.tshooked dans raise / respond / resolve. - ✅ UI buyer :
/account/disputes(index avec status filters) +/account/disputes/[id](unified thread multi-role : seller form + admin form + buyer withdraw button selon le rôle du viewer). - ✅ UI vendor :
/[orgSlug]/~/disputes(index avec deadline highlight). - ✅ UI admin :
/admin/content/marketplace-disputes(queue avec priority sort) +Flagicon dansadmin-routes.tssous "Content" category. - ✅ Cron :
marketplace-maintenanceétendu avec auto-escalation des disputes OPEN > 7j (sellerDeadline trigger) → AWAITING_ADMIN. - ✅
RaiseDisputeDialogshared component (modal avec reason select + description + evidence URLs) wired in/account/orders/[id](PAID + within 14d + no dispute) et/account/deals/[id](escrow funded + buyer only).
Refund automation Stripe
- ✅ Service
services/stripe/refunds.tsrefundOrder:- Crée
stripe.refunds.createavec idempotencyKey unique. - Flip Order status REFUNDED + refundedAt.
- Notify buyer (MARKETPLACE_REFUND_ISSUED).
- Crée
- ✅
adminResolveDisputebranche refundOrder pour REFUND_FULL/PARTIAL (capturestripeRefundId). - ✅ Webhook
charge.refundedsync local Order status (idempotent via updateMany filterstatus: { not: "REFUNDED" }, notify only on first flip).
Timeline events + foundation ML recos v2
- ✅ Schema
ListingEvent+ListingEventKindenum (VIEW, SAVE, UNSAVE, OFFER_CREATED, OFFER_ACCEPTED, OFFER_REJECTED, DEAL_STARTED, DEAL_CLOSED, CHECKOUT_STARTED, ORDER_PAID, REVIEW_POSTED, CONTACT_CLICK, EXTERNAL_CLICK). - ✅ Service
services/marketplace/events.ts:recordListingEvent(best-effort, VIEW dedup par visitor/day).hashVisitor(sha256(ip + ua + day) : rotating daily salt).getListingEventCounts(groupBy kind, last 30d).getDailyListingEventTimeline(raw query Postgres date_trunc).
- ✅ Hooked dans
trackListingView(VIEW),toggleSave(SAVE/UNSAVE),/api/marketplace/offers POST(OFFER_CREATED), webhookcheckout.session.completed(ORDER_PAID).
Helpers
isAdmin()soft check ajouté à@/lib/security/admin-guard(pour les routes qui doivent conditionnellement admin-gate sans redirect).
✅ V2.2 — Related listings + recommendations v1 (livré)
- ✅ "Related listings" sur detail page : 6 cards same-type + tag overlap,
service
src/services/marketplace/related.ts. - ✅ Similarité Jaccard-like sur tags (Postgres
hasSome) + tri tier / featured / reviewsAvg / savesCount. - ✅ Cache Redis 1h par (type, sourceId).
- ✅ Consommé par
/marketplace/[type]/[slug](rail "Related", sous le rail "Customers also viewed" de la V5.1) et exposé par/api/marketplace/recommendations.
🚧 V2.3 — Filtres admin queue + batch actions (3 jours)
- Filtres par type, status, date range, search query.
- Bulk approve/reject avec mass-checkbox.
- Notes internes (visible admin seulement).
✅ V5.1 — KYC pipeline + e-signature scaffold + admin metrics + sponsor (livré cette PR)
V5 polish (chantier précédent dans cette PR)
- ✅ Vendor analytics timeline chart (recharts LineChart sur ListingEvent V4) :
service
getVendorEventTimeline()+ composantEventTimelineChartwired dans/[orgSlug]/~/listings/analytics. - ✅ Per-listing analytics drill-down
/[orgSlug]/~/listings/[id]/analyticsavec window selector (7/14/30/60/90 days) + breakdown par kind. - ✅ Vendor payouts history + balance card via Stripe Connect API
(
getConnectPayoutsSummary+/api/vendor/stripe/connect/payouts). - ✅ Admin-only
adminNotesfield sur MarketplaceListing +AdminNotesPanelinline editor (rendered conditionnellement via isAdmin() server-side gate). - ✅ Collaborative filtering recommendations v2 :
getCoOccurrenceRecommendations()SQL en 2 steps via ListingEvent V4 (WITH actors AS … FROM "ListingEvent" GROUP BY listingId). Cache Redis 1h. Rail "Customers also viewed" AVANT le rail "Related" sur detail page. - ✅ Public marketplace filters : freeOnly / featuredOnly / tier filters ajoutés dans ListingFilters UI + service unified.
- ✅ Dispute badges sur /account/orders index.
- ✅ Vendor dispute banner sur listing detail page (amber alert + Respond link vers /account/disputes/[id]).
- ✅ Refund details bloc sur /account/orders/[id] avec "Refunded on" + délai fonds bancaires.
V5.1 (chantier supplémentaire dans cette PR)
- ✅ KYC pipeline scaffold :
- Schema
KycVerification+ enumsKycStatus(PENDING/IN_REVIEW/VERIFIED/ REJECTED/CANCELLED) +KycProvider(STRIPE_IDENTITY/PERSONA/ONFIDO/MANUAL). - PII report stocké encryptedReport (AES-256-GCM via lib/security/crypto).
- Service
services/marketplace/kyc.ts: startVerification (idempotent, Stripe Identity hosted URL), syncFromProviderSession (webhook handler), getLatestKycForUser + isKycVerified (gate helpers). - API : POST /api/marketplace/kyc/start.
- Schema
- ✅ E-signature scaffold (NDA/LOI/APA) :
- Schema
LegalSignature+ enums LegalDocKind / SignatureStatus / SignatureProvider (DOCUSIGN/HELLOSIGN/BOOSTECOM). - Audit hash sha256(dealId|kind|signerId|isoTs) pour BOOSTECOM internal.
- Service
services/marketplace/legal-signatures.ts: requestSignature, signSignature (auto-advance ndaSignedAt/loiSignedAt/apaSignedAt sur DealThread quand all signed), declineSignature, listSignaturesForDeal. - API : POST /api/marketplace/signatures/[id] (sign/decline), POST /api/vendor/marketplace/deals/[id]/request-signature.
- UI
SignaturesPanel(4 sections : my pending / their pending / signed / declined + request buttons NDA/LOI/APA). Wired dans /account/deals/[id] et /[orgSlug]/~/deals/[id].
- Schema
- ✅ Admin marketplace metrics dashboard :
- Service
getAdminMetrics(): all-time + windowed aggregates (listings, sellers, buyers, offers, deals, orders, revenue, fees, disputes, refunds) + top 10 sellers par saves. - Page
/admin/content/marketplace-metricsavec KPI grid + window selector + top sellers table. Entry dans admin-routes.ts catégorie "Content".
- Service
- ✅ Public seller profile/storefront
/sellers/[id]:- Service
getSellerProfile+listSellerListings. - Page avec avatar + KPIs (listings live, saves, deals closed, orders) + badges trust signals + grid ListingCard. ISR 10min. SEO complete.
- Service
- ✅ Tests vitest :
events.hashVisitor(6 cas) +legal-signaturesaudit hash determinism (4 cas) = 10 tests verts.
🚧 V6 — DocuSign/HelloSign live integration + KYC providers wiring (1-2 semaines)
- ⚙️ Connect activation côté Stripe Dashboard : avant d'aller live, il
faut activer Stripe Connect sur le compte BoostEcom (un click depuis
dashboard.stripe.com/connect). Sans ça,createConnectAccountlèveraaccount_creation_disabled. - ⚙️ Branding Connect : configurer le logo + nom légal sur la page d'onboarding hosted (Stripe Connect → Settings → Branding).
- ⚙️ Webhook endpoint : ajouter
account.updated,payout.failed,charge.refundedaux events listenés sur le webhook existant (/api/webhooks/stripe). Pas de nouvelle URL. - ⚙️ Cron schedule :
marketplace-maintenancedéjà scheduled, pas d'action requise. - ⚙️ E-signature NDA/LOI/APA : DocuSign / HelloSign integration sur
les deal stages LOI_SIGNED / APA_SIGNED. Multi-signataire workflow,
audit trail légal. (Schema déjà préparé via
ndaSignedAt,loiSignedAt,apaSignedAtcolumns sur DealThread.)
🚧 V5 — ce qui reste après V5.1 : embeddings, mobile, API publique (4-8 semaines)
Section placée après V5.1 volontairement : ce qui reste ici est le reliquat de V5, pas un chantier antérieur.
-
Recommendations engine ML : le collaborative filtering est ✅ livré par V5.1 (
getCoOccurrenceRecommendations()sur les ListingEvent). Reste 🚧 : embeddings sémantiques, vector store pgvector, A/B testing infra. -
KYC pipeline : ✅ livré par V5.1 (
services/marketplace/kyc.ts, modèleKycVerification, Stripe Identity hosted). Reste 🚧 : Persona / Onfido, la reprise automatique du verdict provider (aujourd'hui tirée à la main depuis le panel admin, aucun webhook), les auto-tier upgrades selon trust score. -
🚧 Cross-marketplace integrations + mobile (rien de livré) :
-
Import depuis Shopify App Store / ThemeForest.
-
White-label marketplace pour partenaires.
-
React Native app : browse + search + save + push notif.
-
API publique OAuth 2.0 + SDK TypeScript/Python.
Métriques de succès
- Adoption vendor : > 100 listings tiers en V2.
- Conversion : > 3% (view → save), > 1% (save → offer).
- Time to first lead : médiane < 24h après publication.
- Trust : aggregateRating moyenne > 4.0 sur les 100 top listings.
- Search : query latency p95 < 100ms.
- Cache hit rate : > 70% sur
listUnified.